Sum of custom field values from subtasks in parent issue custom groovy field

Hello,

I am displaying an Issue Matrix on the parent issue and would like to display the Sum total of all subtasks (custom field values for field name 'Total') in a custom field underneath the Issue Matrix on the parent issue in a field named 'Request Total'.  Subtask field 'Total' is defined as a Number Field. And the parent Issue field 'Request Total' is defined as a Scripted Field using Template: Number Field.

I think I need to:
1) query the total number of Subtasks
2) get the value for each subtask 'Total' field and store in an array
3) add the values for each subtask 'Total' field (add array)
4) display the SUM of all 'Total' field values (SUM of array) in the 'Request Total' field on the parent Issue

Can anyone please help me get started with coding this.  I am pretty new to JIRA and Groovy scripts, yet have experience, albeit dated experience, in C and VB.

I appreciate any help!

Thanks much,
Scott

 

3 answers

1 accepted

This widget could not be displayed.
Vasiliy Zverev Community Champion Jan 09, 2017

Use this code for scripted field:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.fields.CustomField

//return number subtasks
issue.getSubTaskObjects().size()

CustomField total = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Total")
double totalSum = 0;
for(Issue subtask: issue.getSubTaskObjects()){
    if(subtask.getCustomFieldValue(total) != null)
        totalSum += subtask.getCustomFieldValue(total)
}

return totalSum
Vasiliy,
Thank you so much.  Even though it does throw 'cannot find matching method' it works like a charm. I should have just tried to run it a few minutes sooner smile
Just curious as to why it would throw the error...
Thanks again,
Scott
Vasiliy Zverev Community Champion Jan 09, 2017

Sometimes these warnings are not correct. It is better to use IDE. I use IDEA to write code.

This widget could not be displayed.

Ok, it's a good starting idea, the principles are right, but I would approach it slightly differently

Scripted fields run code and display the result.   When we're looking at doing this, let's start with the issue you're going to display it on.  Groovy lets you get into the JIRA API, so you can use that directly.

Specifically, let's assume you have the issue object for the issue you're going to display the field on.

You can bypass most of your steps very easily:

def listOfSubtasks = issue.getSubTaskObjects ()

Then grab the values you want:

def result = 0
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def totalField = customFieldManager.getCustomFieldObjectByName("Total")

listOfSubtasks.each {

            if (it.getCustomFieldValue(totalField))
                result += (double) it.getCustomFieldValue(totalField)
        }
return result
There is a massive problem with this though - where/when it gets run.  I've used an issue object to abstract it slightly.  Most people doing this think "it's a scripted field, so it'll display the results when I look at an issue", but it's not true.  It displays the last result it calculated.  The script runs effectively when the issue is updated not when you view.  So, if you put this code into a scripted field on the parent issue, it would give you the right answer at first, and it would go wrong when you updated one of the subtasks.  Because the code only runs when the parent is updated.

There are a couple of ways to fix that, but the easiest one is to have a second scripted listener which listens for changes on the sub-tasks and triggers a re-index on the parent when the Total field is changed on one.

Hi @Nic Brough [Adaptavist] ,  I have the same re-indexing issue.

Can you please help me with re-indexing the parent issue if  one custom field is updated on subtask.

I found this code but it seems to be not working in script runner listener.

public class IssueModifiedListener implements InitializingBean, DisposableBean {

        private static final Logger log = Logger.getLogger(IssueModifiedListener.class);

        private final EventPublisher eventPublisher;
        private final IssueIndexManager issueIndexManager;

        /**
         * Constructor.                                                                                                                                                                                                                      
         * @param eventPublisher injected {@code EventPublisher} implementation.                                                                                                                                                             
         */                                                                                                                                                                                                                                  
        public IssueModifiedListener(EventPublisher eventPublisher, IssueIndexManager issueIndexManager) {                                                                                                                                   
                this.eventPublisher = eventPublisher;                                                                                                                                                                                        
                this.issueIndexManager = issueIndexManager;                                                                                                                                                                                  
        }                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                             
        /**                                                                                                                                                                                                                                  
         * Called when the plugin has been enabled.                                                                                                                                                                                          
         * @throws Exception                                                                                                                                                                                                                 
         */                                                                                                                                                                                                                                  
        @Override                                                                                                                                                                                                                            
                public void afterPropertiesSet() throws Exception {                                                                                                                                                                          
                        // register ourselves with the EventPublisher                                                                                                                                                                        
                        eventPublisher.register(this);                                                                                                                                                                                       
                }                                                                                                                                                                                                                            
                                                                                                                                                                                                                                             
        /**                                                                                                                                                                                                                                  
         * Called when the plugin is being disabled or removed.                                                                                                                                                                              
         * @throws Exception                                                                                                                                                                                                                 
         */                                                                                                                                                                                                                                  
        @Override                                                                                                                                                                                                                            
                public void destroy() throws Exception {                                                                                                                                                                                     
                        // unregister ourselves with the EventPublisher                                                                                                                                                                      
                        eventPublisher.unregister(this);                                                                                                                                                                                     
                }                                                                                                                                                                                                                            
                                                                                                                                                                                                                                             
        /**                                                                                                                                                                                                                                  
         * Receives any {@code IssueEvent}s sent by JIRA.                                                                                                                                                                                    
         * @param issueEvent the IssueEvent passed to us                                                                                                                                                                                     
         */                                                                                                                                                                                                                                  
        @EventListener                                                                                                                                                                                                                       
        public void onIssueEvent(IssueEvent issueEvent) {
                Long eventTypeId = issueEvent.getEventTypeId();
                Issue issue = issueEvent.getIssue();
                Issue parent = issue.getParentObject();

                if( parent != null ) {
                        reindexIssue( parent );
                }

        }

        /**
         * Called a parent issue is found that needs to be reindexed.
         * @param issue The issue to be reindexed
         */
        private void reindexIssue(Issue issue) {
                try {
                        boolean origVal = ImportUtils.isIndexIssues();
                        ImportUtils.setIndexIssues(true);
                        issueIndexManager.reIndex(issue);
                        ImportUtils.setIndexIssues(origVal);
                } catch (IndexException ie) {
                        log.error("Unable to reindex issue: " + issue.getString("key")
                                        + ", [id=" + issue.getLong("id") + "].", ie);
                }
        }

}

This widget could not be displayed.

Hey Nic,

Thank you so much for your response. I did not pursue your answer as Vasily's answer works. Yet I do thank you!

Scott

 

His code is much better than mine (as usual), but does the same thing in much the same way.

Did you pick up the bit about causing the parent issue to re-index?

I may be wrong but I think that in this case it may not be an issue.  The project only has one issue type (IT Sourcing Intake Request Form) and a subtask (IT Sourcing Intake Request Form Subtask). Create only provides the IT Sourcing Intake Request Form as an option.  Users will be trained to create subtask to represent orders under the parent issue (requisition). As of now the scripted field is only displayed on the View Issue intake form, as I thought if I placed it there it would force the field to trigger.  So far in my testing when I update a subtask, the view issue screen updates the Total.  I'll keep pocking at it.  Thanks again!  -Scott 

Suggest an answer

Log in or Sign up to answer
Atlassian Summit 2018

Meet the community IRL

Atlassian Summit is an excellent opportunity for in-person support, training, and networking.

Learn more
Community showcase
Posted yesterday in Teamwork

What teamwork quotes inspire you?

Hey everyone! My name is Natalie and I'm an editor of the Atlassian Blog and I've got a question for you: What's your favorite quote about teamwork?  We've compiled a list here, along with...

109 views 15 7
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