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.
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 .
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
@Jeremy Gaudet , yep, as you point out the update statement should be outside loop, thanks
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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); } }
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I get this error on the following line
changedItem.each { item ->
expecting '}' found '-' I didn't change ur code, any thoughts?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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 -> 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 must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
And, to give credit where due, this is modified/updated from:
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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..!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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);
}
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Online forums and learning are now in one easy-to-use experience.
By continuing, you accept the updated Community Terms of Use and acknowledge the Privacy Policy. Your public name, photo, and achievements may be publicly visible and available in search engines.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.