Scripted Field - Count All Issues in Epic with a status of X

Trevor Hunt
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.
November 11, 2013

This is my first shot at a scripted field and I have no clue where to start. An answer to the subject will go a long way in getting me where I'm trying to go.

Ultimately, I want to count all issues within an epic or all subtasks within an issue. Then I want to count all issues with a specific status - lets assume closed - and divide this by the total number of subtasks to get a rough % Complete.

7 answers

1 accepted

6 votes
Answer accepted
Henning Tietgens
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.
November 12, 2013

For Stories linked to the Epics try this:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.link.IssueLinkManager
import com.atlassian.jira.issue.link.IssueLinkType
import com.atlassian.jira.issue.link.IssueLinkTypeManager

Issue issue = issue

IssueLinkTypeManager issueLinkTypeManager = ComponentAccessor.getComponent(IssueLinkTypeManager)
IssueLinkManager issueLinkManager = ComponentAccessor.issueLinkManager
Collection<IssueLinkType> storyLinkTypes = issueLinkTypeManager.getIssueLinkTypesByName('Epic-Story Link')
if (storyLinkTypes) {
    Long storyLinkTypeId = storyLinkTypes[0].id
    def linkedStories = issueLinkManager.getOutwardLinks(issue.id).findAll{it.linkTypeId==storyLinkTypeId}*.destinationObject

    int numStories = linkedStories?.size()?:0
    if (numStories>0) {
        int numClosedStories = linkedStories?.count{it?.statusObject?.name=='Closed'}?:0
        return (numClosedStories/numStories) as Double
    } else {
        return 0 as Double
    }
} else {
    return 0 as Double
}

This is not tested by me, so you should try this in a test environment first.

The first part, getting the linkTypeId could be replaced by the id of the link type in your system if you don't want to use the script in different systems (test, live, etc.).

Trevor Hunt
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.
November 12, 2013

I just noticed some strange behavior. When I close or reopen an issue, the modal/transition window that pops up doesn't go away when clicking the button.

Any idea what might be causing this? I removed the scripted field to confirm it's the cause.

1 vote
Trevor Hunt
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.
November 12, 2013

Thanks for this goes out to Henning Tietgens.

I'm sure i've mangled his code, but here's what I ended up with.

Goal: calculate a % of Tasks Complete on the host issue by looking at sub-tasks in the case of stories/issues and stories within an epic in the case of Epics.

First I'm checking whether if the host issue is an Epic or not. If it is, use the linked stories approach to calculate the %Complete. Otherwise, use the sub-task approach:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.link.IssueLinkManager
import com.atlassian.jira.issue.link.IssueLinkType
import com.atlassian.jira.issue.link.IssueLinkTypeManager

Issue issue = issue
IssueLinkTypeManager issueLinkTypeManager = ComponentAccessor.getComponent(IssueLinkTypeManager)
IssueLinkManager issueLinkManager = ComponentAccessor.issueLinkManager
Collection<IssueLinkType> storyLinkTypes = issueLinkTypeManager.getIssueLinkTypesByName('Epic-Story Link')

if (issue.issueTypeObject.name == "Epic"){
    if (storyLinkTypes) {
        Long storyLinkTypeId = storyLinkTypes[0].id
        def linkedStories = issueLinkManager.getOutwardLinks(issue.id).findAll{it.linkTypeId==storyLinkTypeId}*.destinationObject
     
        double numStories = linkedStories?.size()?:0
        if (numStories>0) {
            double numClosedStories = linkedStories?.count{it?.statusObject?.name=='Closed'}?:0
            double percentcomplete = (numClosedStories/numStories)*100
            return percentcomplete.round(2) as double
        } else {
            return 0 as double
        }
    }
    
} else{
    double numSubtasks = issue.getSubTaskObjects()?.size()?:0
    if (numSubtasks>0) {
        double numClosedSubtasks = issue.getSubTaskObjects()?.count{it?.statusObject?.name=='Closed'}?:0
        double percentcomplete = (numClosedSubtasks/numSubtasks)*100
        return percentcomplete.round(2) as double
    } else {
        return 0 as double
    }
}

Henning Tietgens
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.
November 12, 2013

Does this work? You return object of different classes: String, Double and int.

If you want to use the field for reports it should be a number field (so return a Double).

Henning Tietgens
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.
November 12, 2013

Did you get any errors in the log?

Trevor Hunt
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.
November 12, 2013

Unfortunately, I have no access to the logs.

Current field config is:

Template: Number Field
Configured searcher: Number Searcher

Trevor Hunt
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.
November 12, 2013

It works, but I'll be happy to "fix" anything that's wrong. I did find that when I transition an issue to closed the modal just stays up when I have this scripted field enabled.

Henning Tietgens
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.
November 12, 2013

What's the config of the custom field searcher and template for your scripted field?

Trevor Hunt
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.
November 12, 2013

Found the answer. I thought re-indexing might help so I ran one and got an error that referenced doubles. I just needed to ensure I set both sections to return doubles as you pointed out above.

1 vote
Renjith Pillai
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.
November 11, 2013

Rather than this, why dont you use the Epic Charts?

Trevor Hunt
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.
November 11, 2013

Unfortunately, I need to be able to report on this field.

0 votes
Szymon F June 24, 2020

Hi all, 

I can see this topic is as old as hills and something definitely changed but I am facing a problem with the code posted by @Henning Tietgens .

I am trying to get a field that shows the <Done>/<All> tickets in Epic (including Bugs, US and Tasks) where Done is statusCategory = Done

I started from the very basic solution: https://community.atlassian.com/t5/Jira-Software-questions/JIRA-Need-count-of-stories-under-an-epic/qaq-p/961579

but I always get an error:

Screenshot 2020-06-24 at 10.38.55.png

At the end I will just change the percentage to String: Done/All

Screenshot 2020-06-24 at 10.46.29.png

Henning Tietgens
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.
June 24, 2020

Hi, you're getting the status category of the issue and after that count every subtask of the issue where the status category of the issue (not the subtask) is Done. Is that what you want? In this case on simple if statement after getting sc would be enough.

if (sc.name == 'Done') {

If you want the status category of the subtask you need to change {sc.getName()... to {it.status.statusCategory.name == 'Done'}.

If you write "as Integer" at the end of the last line the static type error should go away.

Szymon F June 25, 2020

Hi Henning, thank you for your replay. I am almost there.

 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.link.IssueLinkManager
import com.atlassian.jira.issue.link.IssueLinkType
import com.atlassian.jira.issue.link.IssueLinkTypeManager

Issue issue = issue

IssueLinkTypeManager issueLinkTypeManager = ComponentAccessor.getComponent(IssueLinkTypeManager)
IssueLinkManager issueLinkManager = ComponentAccessor.issueLinkManager
Collection<IssueLinkType> storyLinkTypes = issueLinkTypeManager.getIssueLinkTypesByName('Epic-Story Link')
if (storyLinkTypes) {
Long storyLinkTypeId = storyLinkTypes[0].id
def linkedStories = issueLinkManager.getOutwardLinks(issue.id).findAll{it.linkTypeId==storyLinkTypeId}*.destinationObject

int numStories = linkedStories?.size()?:0
if (numStories>0) {
def numClosedStories = linkedStories?.count{it.status.statusCategory.name == 'Done'}?:0
// return (numClosedStories/numStories) as Double
return numClosedStories+"/"+numStories as String
} else {
return 0 as Double
}
} else {
return 0 as Double
}

It returns the 0/32 (0 is the number of Done tickets - false I have 12 "/" 32 is a number of all tickets under Epic and this is true

Szymon F June 25, 2020

I was able to find the problem, for Jira there is no such a category as Done (we are using it) for Atlassian it is COMPLETE. So the code is as follows:

 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.link.IssueLinkManager
import com.atlassian.jira.issue.link.IssueLinkType
import com.atlassian.jira.issue.link.IssueLinkTypeManager

Issue issue = issue

IssueLinkTypeManager issueLinkTypeManager = ComponentAccessor.getComponent(IssueLinkTypeManager)
IssueLinkManager issueLinkManager = ComponentAccessor.issueLinkManager
Collection<IssueLinkType> storyLinkTypes = issueLinkTypeManager.getIssueLinkTypesByName('Epic-Story Link')
if (storyLinkTypes) {
Long storyLinkTypeId = storyLinkTypes[0].id
def linkedStories = issueLinkManager.getOutwardLinks(issue.id).findAll{it.linkTypeId==storyLinkTypeId}*.destinationObject

int numStories = linkedStories?.size()?:0
if (numStories>0) {
def numClosedStories = linkedStories?.count{it.status.statusCategory.name == 'Complete'}?:0

return numClosedStories+"/"+numStories as String
} else {
return 0 as Double
}
} else {
return 0 as Double
}
Henning Tietgens
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.
June 25, 2020

Ok, perfect. You should double check the return types. Something is wrong, because sometimes it's String sometimes Double. This should be the same, I suspect

return ""

or

return null

instead of

return 0 as Double

Szymon F June 25, 2020
return ""

works like a charm :)

 

Thank you! 

Russell Bailey July 9, 2020

When I try the above script, I get an error:

variable [issue] is undeclared

Szymon F July 10, 2020

Probably you are trying to query it in Console not in the Script Field. Variable is undeclared because it is not fixed

0 votes
Trevor Hunt
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.
November 12, 2013

Unfortunately, no access to logs at the moment.

I made the changes you recommended:

Template: Number Field
Configured searcher: Number Searcher

0 votes
Renjith Pillai
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.
November 12, 2013

You can also make an Issue Statistics gadget based on the JQL that uses Epic Link.

For example see https://jira.atlassian.com/issues/?jql=%22Epic%20Link%22%20%3D%20GHS-5232%20and%20status%20%3D%20Resolved

Trevor Hunt
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.
November 12, 2013

Thanks for the suggestion! This is an approach I use in dashboards and sometimes in Confluence pages. The problem I'm tasked with is providing a field that's reportable so it can be included on an excel export.

Renjith Pillai
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.
November 13, 2013

Ah alright. In that case the scripted field solution from Henning Tietgens is the best solution.

0 votes
Henning Tietgens
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.
November 11, 2013

For a Script Runner scripted field you can try this:

int numSubtasks = issue.getSubTaskObjects()?.size()?:0
if (numSubtasks&gt;0) {
    int numClosedSubtasks = issue.getSubTaskObjects()?.count{it?.statusObject?.name=='Closed'}?:0
    return (numClosedSubtasks/numSubtasks) as Double
} else {
    return 0 as Double
}

Configure as nuber template and searcher.

Trevor Hunt
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.
November 11, 2013

Very close... it doesn't appear to work for epics and their sub issues.

Is the above script based on Groovy?

Henning Tietgens
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.
November 12, 2013

Yes, it's groovy. Ok, so your talking about Stories linked to Epics, not subtasks?

Suggest an answer

Log in or Sign up to answer