Sync Parent from Child based on Updated Values

Instead of having one line of post function to "copy field value to Parent" issue, I really, really REALLY want a Scripted Function that reads custom fields on a sub-task for change, and if changed carries that value over to the parent. I cannot figure out how to do this.

This would save us countless errors in accuracy (fields not being added to older workflows) not to mention hours of recreating our workflows when we merge our current instance into our goal instance.

Gratuitous Math: We have 20 workflows with about 10-15 steps each, and around 136 custom fields - we have an average of 55 applicable to each workflow step transition, of which there are an average of 4. 20*10=200 transitions. 55*(10*4)=2200 custom field lines. 200*2200=44,0000 lines to write back in for all transitions.

4 answers

Hi, The answer provided by @Jeremy Gaudet contains all the things you need to get the job done. The below script is possibly what you are looking for [thanks to Jeremy]. IT copies all custom fields change in sub task to the parent task:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.MutableIssue

def customFieldManager = ComponentAccessor.getCustomFieldManager()
def issueManager = ComponentAccessor.getIssueManager()
def changeHistoryManager=  ComponentAccessor.getChangeHistoryManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def issue = event.getIssue()

if(issue.getIssueType().isSubTask()){
    //get the parent issue
    def parentIssue = issue.parentObject
    //get all the custom fields
    def customFields = customFieldManager.getCustomFieldObjects(issue)

    //get the changed item list from change log
    def changeLogs = event.getChangeLog()
    def changedItem = changeHistoryManager.getAllChangeItems(issue).findAll { it.changeGroupId == changeLogs.id}

    //update parent issue if the changed item is a custom field
    changedItem.each { item ->
        if(customFields*.name.contains(item.field)) {
            def customFieldName = item.field
            def customField = customFieldManager.getCustomFieldObjectByName(customFieldName)
            def customFieldValue = issue.getCustomFieldValue(customField)

            //mutating parent issue
            MutableIssue mutableParentIssue = issueManager.getIssueObject(parentIssue.id)
            mutableParentIssue.setCustomFieldValue(customField, customFieldValue)
            //updating the parent issue
            issueManager.updateIssue(user, mutableParentIssue, EventDispatchOption.DO_NOT_DISPATCH, false);
        }
    }
}

  

Create a custom listener with "Issue Updated" event and test the script before deploying in production. 

 

You probably don't want to do the issueManager.updateIssue() in the loop, as then you'll get one edit event/history per changed field.  It would be (slightly) better to track if there were any edits, and issue that outside of the loop if there were.

Either way, thanks for making it automatically pick up all custom field changes smile.

What does that mean... "issueManager.updateIssue()" instead to track if there are edits?

As you can probably tell, I'm more app admin than sys admin smile

 

I mean this line:

issueManager.updateIssue(user, mutableParentIssue, EventDispatchOption.DO_NOT_DISPATCH, false);

Should only be done once at the end, not multiple times in the middle.

@Jeremy Gaudet , yep, as you point out the update statement should be outside loop, thanks smile

As I am completely new to this, where does that line go? cheeky

Here is the updated code as suggested. But please test the code before you use in production:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption

def customFieldManager = ComponentAccessor.getCustomFieldManager()
def issueManager = ComponentAccessor.getIssueManager()
def changeHistoryManager=  ComponentAccessor.getChangeHistoryManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def issue = event.getIssue()

if(issue.getIssueType().isSubTask()){
    //get the parent issue
    def parentIssue = issue.parentObject
    //get all the custom fields
    def customFields = customFieldManager.getCustomFieldObjects(issue)

    //get the changed item list from change log
    def changeLogs = event.getChangeLog()
    def changedItem = changeHistoryManager.getAllChangeItems(issue).findAll { it.changeGroupId == changeLogs.id}

    //mutating parent issue
    def mutableParentIssue = issueManager.getIssueObject(parentIssue.id)
    //update parent issue if the changed item is a custom field
    changedItem.each { item ->
        if(customFields*.name.contains(item.field)) {
            def customFieldName = item.field
            def customField = customFieldManager.getCustomFieldObjectByName(customFieldName)
            def customFieldValue = issue.getCustomFieldValue(customField)
            mutableParentIssue.setCustomFieldValue(customField, customFieldValue)
        }
    }
    //updating the parent issue only if condition is true
    if(mutableParentIssue.getModifiedFields()){
        issueManager.updateIssue(user, mutableParentIssue, EventDispatchOption.DO_NOT_DISPATCH, false);
    }
}

 

 

Thanks so much! You all are the best smile 

Hasan, Can you help me?

I need to update the parent Date/time field with current timestamp when a child task is done.

 

Need: Aging charts , like To track how long it took for testing to being after coding task is done, etc

 

The question is posted here. you can provide answer there.

https://community.atlassian.com/t5/Questions/How-to-update-a-parent-field-when-subtsk-state-changes/qaq-p/640019

@Ashraful Hasan [Adaptavist]

I get this error on the following line

changedItem.each { item ->

expecting '}' found '-' I didn't change ur code, any thoughts? 

@Ashraful Hasan [Adaptavist]

I created a post function which will copy parents summary and description to custom fields called parent summary and parent description on a subtask. It happens whenever a new subtask is created under the parent.

Now i have an ask that whenever summary or description is updated, it should update in both places, if updated in the parent, then update should reflect in subtask and vice versa.

Am trying to use your code, for error -&gt i useed-> and error got cleared, but am not sure if your code works in my case or not. trying to understand and troubleshoot.

You could add a Script Runner script listener to accomplish this.  Just listen for Issue Updated (or multiple additional events if the custom fields are present on the screens for the transitions that emit those events), and update the parent if any of the custom fields are present in the list of changed items.  I've done this in the opposite direction, but I have a fairly comprehensive example that updates the child's FixVersion (and a custom field called "Verified Version/s") if the child is changed to put it out of sync, or if the parent is changed.  It should be straightforward to extend it.  I have it listening on all projects, and the use the workflow to determine if it should fire, that way the listener config doesn't need to be modified when new projects are added.  Here it is, in case you find it helpful:

package com.myorg.listeners;

import org.apache.log4j.Category;
import com.atlassian.jira.ComponentManager;
import com.atlassian.jira.event.issue.AbstractIssueEventListener;
import com.atlassian.jira.event.issue.IssueEvent;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.project.version.Version;
import com.atlassian.jira.config.SubTaskManager;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.issue.CustomFieldManager;
import com.atlassian.jira.issue.fields.CustomField;
import com.atlassian.jira.event.type.EventDispatchOption;
import com.atlassian.jira.workflow.WorkflowManager;
import com.atlassian.jira.workflow.JiraWorkflow;
import java.util.ArrayList;
import java.util.Collection;

class SyncParentFixVerifiedVersionsToSubTask extends AbstractIssueEventListener 
{
    Category log = Category.getInstance("com.onresolve.jira.groovy");
    SubTaskManager subTaskManager = ComponentManager.getInstance().getSubTaskManager();
    WorkflowManager workflowManager = ComponentManager.getInstance().getWorkflowManager();
    IssueManager issueManager = ComponentManager.getInstance().getIssueManager();
    CustomFieldManager customFieldManager = ComponentManager.getInstance().getCustomFieldManager();

    Boolean changed = false;
    Long workflowId;

    @Override
    void workflowEvent(IssueEvent event) {
        try {
            if (subTaskManager.isSubTasksEnabled()) {
                Issue eventIssue = event.getIssue();
                JiraWorkflow workflow = workflowManager.getWorkflow(eventIssue);
                if (workflow.getName() == "Put Workflow Name Here") {
                    CustomField vvcf = customFieldManager.getCustomFieldObjectByName("Verified Version/s");
                    if ( !eventIssue.getIssueTypeObject().isSubTask() ) {
                        // Change is on a potential parent, sync to sub-tasks if there are any
                        Collection<Issue> subTasks = eventIssue.getSubTaskObjects();
                        List changeItems = event.getChangeLog().getRelated("ChildChangeItem");

                        // Sync FixVersion/s
                        if( changeItems.any {it.get('field')=='Fix Version'} ) {
                            changed = true;
                            // Collection<Version> fixVersions = new ArrayList<Version>();
                            // fixVersions = eventIssue.getFixVersions();
                            Collection<Version> fixVersions = eventIssue.getFixVersions();
                            if (!subTasks.isEmpty()) {
                                subTasks.each {
                                    it.setFixVersions(fixVersions);
                                }                     
                            }
                        }

                        // Sync Verified Version/s
                        if( changeItems.any {it.get('field')=='Verified Version/s'} ) {
                            changed = true;
                            Collection<Version> verifiedVersions = new ArrayList<Version>();
                            verifiedVersions = eventIssue.getCustomFieldValue(vvcf);
                            if (!subTasks.isEmpty()) {
                                subTasks.each {
                                    it.setCustomFieldValue(vvcf,verifiedVersions);
                                }
                            }
                        }

                        if (changed) {
                            subTasks.each {
                                issueManager.updateIssue(event.getUser(), it, EventDispatchOption.ISSUE_UPDATED, false);
                            }
                        }
                    } else {
                        // Change is on a sub-task, sync from the parent if they are out of sync
                        Issue parentIssue = eventIssue.getParentObject();

                        // Sync "Fix Version/s"
                        Collection<Version> parentFixVersions = parentIssue.getFixVersions();
                        if (parentFixVersions == null) parentFixVersions = new ArrayList<Version>();
                        Collection<Version> fixVersions = eventIssue.getFixVersions();
                        if (fixVersions == null) fixVersions = new ArrayList<Version>();
                        if(!(parentFixVersions.containsAll(fixVersions) && fixVersions.containsAll(parentFixVersions))) {
                            eventIssue.setFixVersions(parentFixVersions);
                            changed = true;
                        }

                        // Sync "Verified Version/s"
                        Collection<Version> parentVerifiedVersions = parentIssue.getCustomFieldValue(vvcf);
                        if (parentVerifiedVersions == null) parentVerifiedVersions = new ArrayList<Version>();
                        Collection<Version> verifiedVersions = eventIssue.getCustomFieldValue(vvcf);
                        if (verifiedVersions == null) verifiedVersions = new ArrayList<Version>();
                        if(!(parentVerifiedVersions.containsAll(verifiedVersions) && verifiedVersions.containsAll(parentVerifiedVersions))) {
                            eventIssue.setCustomFieldValue(vvcf,parentVerifiedVersions);
                            changed = true;
                        }
                        if (changed) {
                            issueManager.updateIssue(event.getUser(), eventIssue, EventDispatchOption.ISSUE_UPDATED, false);
                        }
                    }
                }
            }
        }
        catch (ex) {
            log.debug "Event: ${event.getEventTypeId()} fired for ${event.issue} and caught by SyncParentFixVerifiedVersionsToSubTask"
            log.debug (ex.getMessage())
        }
    }
}

The main difference would be updating the parent instead of the child, but the second block covers getting the parentIssue, and so this should contain examples of everything you need.

Based on "It should be straightforward to extend it." I want to make sure I am reading this correctly... So this would need to be written/extended for each custom field we have, though, right? My point here is that we have ~136 custom fields. It'd be a really long script..! sad 

 

0 votes

Just to clarify, you want to copy all custom fields on subtasks upwards to its parent (where the CF is associated with both parent and child), when any CF on the subtask changes ?

Is this really useful? What happens when an issue has multiple subtasks, if the CF is changed on one, it will have the same value as its parent, but not the same value as its sibling subtasks.

If the subtask CFs should be the same as the parent CFs, isn't it easier to only put the CFs on the parent issue? Script fields or web panels can make the values show on the child issue if that's the desire.

Jamie:

By design, we only ever have one sub-task per parent issue. This is due to integration with a customer portal program that we have. We work on the sub-task issues, not the parent - so to have the CFs only on the parent level isn't really the most convenient/effective/efficient for our use.

So in essence, yes, all custom fields on sub-tasks upwards to its parent when any CF on the sub-task changes.

I did find a plugin for this but it seems to be only contingent on workflow steps which isn't ideal.

Hi All,

I am battling with writing a script listener that will update the parent issue's comment with the sub tasks comments whenever a sub task is added. I currently have the following script:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption

def commentManager = ComponentAccessor.getCommentManager()

def issueManager = ComponentAccessor.getIssueManager()
def commentManger = ComponentAccessor.getCommentManager()
def changeHistoryManager= ComponentAccessor.getChangeHistoryManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def issue = event.getIssue()

if(issue.getIssueType().isSubTask()){
//get the parent issue
def parentIssue = issue.parentObject
//get last comment
def comments = commentManager.getLastComment(issue)

//get the changed item list from change log
def changeLogs = event.getChangeLog()
def changedItem = changeHistoryManager.getAllChangeItems(issue).findAll { it.changeGroupId == changeLogs.id}

//mutating parent issue
def mutableParentIssue = issueManager.getIssueObject(parentIssue.id)

//mutating parent comment
def mutableParentComment = commentManger.getMutableComment(Long commentId)

//updating the parent issue only if condition is true
if(mutableParentIssue.getModifiedFields()){
issueManager.updateIssue(user, mutableParentIssue, EventDispatchOption.DO_NOT_DISPATCH, false);
}
}

Suggest an answer

Log in or Join to answer
Community showcase
Sarah Schuster
Posted Jan 29, 2018 in Jira

What are common themes you've seen across successful & failed Jira Software implementations?

Hey everyone! My name is Sarah Schuster, and I'm a Customer Success Manager in Atlassian specializing in Jira Software Cloud. Over the next few weeks I will be posting discussion topics (8 total) to ...

2,831 views 12 18
Join discussion

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you
Atlassian Team Tour

Join us on the Team Tour

We're bringing product updates and pro tips on teamwork to ten cities around the world.

Save your spot