I love Jira, probably more than I should, but the difficulty in tracking progress and breaking down work to mirror my teams kills me. The new Greenhopper 6.2 and the “edit in place” fields made a big difference, but I still needed a couple of things:
- A way to groups a bunch of Epics together into a larger trackable Epic
- A way to track Total Story Points/Open Story points for my Epics
With this, I could get automated Gantt charts from my system that kept my corporate overlords happy, and didn’t bog myself or my team with extra tracking.
I used the jPlugs Gantt chart plugin. While not free, it was affordable, flexible, and did what I needed. Best of all, it also has a Confluence plugin, so my project status pages could be updated via Jira.
Next, I needed to make some custom fields, so I installed JIRA Misc Custom Field. This free swiss army knife of Jira custom fields makes up for tricky implementation by super-flexibility and a dedicated following of users who post all sorts of tips and howtos, which you will need because the documentation is sparse.
Next I created a Calculated Number Field, using the following Code:
The sum of story points for children tasks <!-- @@Formula: import com.atlassian.jira.component.ComponentAccessor; import com.atlassian.jira.issue.Issue; import com.atlassian.jira.issue.fields.CustomField; import com.atlassian.jira.issue.CustomFieldManager; import com.atlassian.jira.issue.link.IssueLink; import com.atlassian.jira.issue.link.IssueLinkManager; import com.atlassian.jira.issue.issuetype.IssueType; import java.util.Arrays; import java.util.ArrayList; import java.lang.Double; import java.util.List; // SWAP THIS WITH THE FIELD WHERE YOU STORE STORY POINTS private static long STORY_POINT_FIELD = 10007; private static String STORY_NAME = "Story"; private static String EPIC_NAME = "Epic"; // I ONLY WANT TO INCLUDE CHILDREN THAT ARE STORIES // OR ARE DEPENDENT EPICS private static String[] LEGAL_SUB_EPIC_LINKS = {"Dependency", "Epic-Story Link"}; // TRACK ALL THE EPICS CHECKED TO AVOID FOLLOWING CIRCULAR REFS private List checkedEpics = new ArrayList(); /** @return the number of story points for a story **/ int getStoryPointsForStory(Issue theIssue) { int rv = 0; CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager(); CustomField storyCF = customFieldManager.getCustomFieldObject(STORY_POINT_FIELD); if(theIssue.getCustomFieldValue(storyCF) != null) { try { rv = Double.valueOf(theIssue.getCustomFieldValue(storyCF)).intValue(); } catch (Exception e) { } } return rv; } /** @return the sum of story points for the given issue **/ int sumChildrenStoryPoints(Issue theIssue) { int rv = 0; String issueTypeName = theIssue.getIssueTypeObject().getName(); // IF THE ISSUE IS A STORY, RETURN THE STORY POINTS if (STORY_NAME.equals(issueTypeName)) { rv = getStoryPointsForStory(theIssue); // BAIL EARLY ON NON-EPICS } else if (!EPIC_NAME.equals(issueTypeName)) { rv = 0; // IF IT IS AN EPIC, MAKE SURE WE HAVE NOT CHECK THIS EPIC BEFORE } else if (!checkedEpics.contains(theIssue.getId())){ // REMEMBER TO AVOID CIRCULAR DEPENDENCIES checkedEpics.add(theIssue.getId()); IssueLinkManager issueLinkManager = ComponentAccessor.getIssueLinkManager(); List legalLinks = Arrays.asList(LEGAL_SUB_EPIC_LINKS); for(IssueLink link : issueLinkManager.getOutwardLinks(theIssue.getId())) { if(legalLinks.contains(link.getIssueLinkType().getName())) { rv = rv + sumChildrenStoryPoints(link.getDestinationObject()); } } } return rv; } sumChildrenStoryPoints(issue.getIssueObject()); --> <!-- @@Format: numberTool.format(value) -->
To get a sum of the open points, I copied and pasted ( know…lame) a second calculated number field with the same code. I added a filter to the function getStoryPointsForStory:
// BAIL EARLY WITH 0 FOR CLOSED STORIES List closedStatuses = Arrays.asList({"Closed", "Resolved"}); if (closedStatuses.contains(theIssue.getStatusObject().getName())) { return 0; }
This returns them with zero points against the open total. Finally, I made a progress field as anotherĀ calculated number field with:
Progress as a percentage of closed story points/total story points <!-- customfield_10336 = sum of open children story points customfield_10344 = sum of all children story points --> <!-- @@Formula: ( ((issue.get("customfield_10334") != null && issue.get("customfield_10336") != null)? ((issue.get("customfield_10334") - issue.get("customfield_10336"))/issue.get("customfield_10334")*100) : 0) ).intValue() --> <!-- @@Format: numberTool.format(value) -->
You will have to update the customfield numbers for your system. No I set my jPlugs progress to track this final custom field. As people close their stories, I watch my Gantt chart update.
Very cool – it works perfectly! But I have an issue withe the Filter for the Bail out of Closed Issues… can’t get it work.
where to I have to put that code exactly?
I put it at the beginning of the function sumChildrenStoryPoints. It should see that the story is in a closed state and return a 0 immediately.