Custom Sprint Report: getting sprint info from Java API (JIRA Software v7+ server)

Caela Northey December 13, 2017

Summary

I wanted to share some of my trials and tribulations while working on a custom sprint report for Jira Software (v7.3) server using the Plugins2 framework, in case there were others trying to do the same.

Context

I started this project before knowing that the agile features of JIRA Software are not exposed in the Java API. After some digging found suggestions to use the REST API, which did have access to agile entities. This involved building a little HTTP client in my code from scratch (since Jira Java REST API Client is not supported past JIRA v6 because of course it isn't).

Important part

While I was testing my client however, I found that in fact, you can get a good amount of information about sprints from the Java API using the getValue() method of the CustomField class. That is, you can get access to an instance of CustomField object that represents an issue's sprints from the CustomFieldManager class (provided by the ComponentAccessor class, for example), and then call the getValue() method, which returns a list of info about the sprint. You can then parse the list for the needed info (using regex patterns, for example).

An example of the output of getValue() is shown below. This issue was in two sprints, Sprint 1 and Sprint 2. Sprint 1 is closed and started on 2017-05-31, while Sprint 2 is a Future sprint with no start date, etc.

[com.atlassian.greenhopper.service.sprint.Sprint@77e7bf34[id=225,rapidViewId=161,state=CLOSED,name=Sprint 1,startDate=2017-05-31T10:31:07.779-05:00,endDate=2017-06-07T10:31:00.000-05:00,completeDate=2017-12-11T14:12:16.082-06:00,sequence=225], com.atlassian.greenhopper.service.sprint.Sprint@68792939[id=400,rapidViewId=161,state=FUTURE,name=Sprint 2,startDate=,endDate=,completeDate=,sequence=400]]

Conclusion

If the report you're making requires any of this info, you don't need to access the REST API, and can get it from the Java API.

  • Sprint name
  • Agile board ID
  • Sprint state
  • Sprint start date
  • Sprint end date
  • Sprint completed date

I hope this info is helpful for anyone in my position who just wants to make some basic custom sprint reports!


References

4 comments

Comment

Log in or Sign up to comment
Michael Nicholson November 1, 2018

Thanks for posting! This got me started on the right track. I ended up also going down the rabbit hole of casting the getValue() Object to an  ArrayList<Sprint> instead of parsing the toString() output.

georgiossalon November 12, 2018

Could you please paste the part of your code with the CustomFields here?

 

I am getting a similar output by using:

 IssueField field = issue.getFieldByName("Sprint");

field.getValue();

 

The problem is, I do not know how I could extract the attributes from the following output:

 

[com.atlassian.greenhopper.service.sprint.Sprint@77e7bf34[id=225,rapidViewId=161,state=CLOSED,name=Sprint 1,startDate=2017-05-31T10:31:07.779-05:00,endDate=2017-06-07T10:31:00.000-05:00,completeDate=2017-12-11T14:12:16.082-06:00,sequence=225], com.atlassian.greenhopper.service.sprint.Sprint@68792939[id=400,rapidViewId=161,state=FUTURE,name=Sprint 2,startDate=,endDate=,completeDate=,sequence=400]]

 

Any suggestion?

Michael Nicholson November 12, 2018

The easy way is just to parse the string returned from getValue().toString() - what you pasted.

Alternatively, after a little extra work you can cast the object returned by getValue() to what it actually is: an ArrayList<Sprint>. This requires adding a dependency on the library that contains the definition of the Sprint class: com.atlassian.greenhopper.service.sprint.*. This page goes into detail on that topic https://community.atlassian.com/t5/Answers-Developer-Questions/Access-com-atlassian-greenhopper-Java-API/qaq-p/566539

Like georgiossalon likes this
georgiossalon November 12, 2018

Many thanks for replying.

 

1 Method)

By parsing you mean, writing my self a method that tries to get the attributes somehow from the String? 

 

2 Method)

I have tried the following:

ArrayList<Sprint> sprints = field.getValue();

but I am getting the following error:

Incompatible types.
Required: java.util.ArrayList<com.atlassian.greenhopper.service.sprint.Sprint>
Found: java.lang.Object

 

and 

ArrayList<Sprint> sprints = (ArrayList<Sprint>) field.getValue();

Exception in thread "main" java.lang.ClassCastException:

class org.codehaus.jettison.json.JSONArray cannot be cast to class java.util.ArrayList (org.codehaus.jettison.json.JSONArray is in unnamed module of loader 'app'; java.util.ArrayList is in module java.base of loader 'bootstrap')

With 

field.getValue().getClass() 

 I am getting ---> org.codehaus.jettison.json.JSONArray

After trying though

JSONArray jsonArray = new JSONArray(field.getValue());

I am getting --> Cannot resolve constructor 'JSONArray(java.lang.Object)' 

I am kind of lost right now.

Michael Nicholson November 16, 2018

1 Method )

Yes. Using a regex string as suggested in the original post.

2 Method)

I will just paste my example code. This may not be drop-in-ready in your environment, but should provide you enough context to get you over the hump. Note you will need to `import com.atlassian.greenhopper.service.sprint.*;` which requires following the directions in the original link I posted.

public void validate(Map transientVars, Map args, PropertySet ps) throws InvalidInputException {
Issue issue = (Issue) transientVars.get("issue");
log.debug("Processing issue '" + issue.getKey() + "'");

List<CustomField> customFields = customFieldManager.getCustomFieldObjects(issue);
log.debug("Number of fields in issue '" + customFields.size() + "'");

for (CustomField field : customFields) {
log.debug("Checking field name '" + field.getFieldName() + "'");

if (field.getFieldName().equals("Sprint")) {
log.debug("Found 'Sprint' field");

Object fieldValue = field.getValue(issue);

if (fieldValue == null) {
log.debug("Sprint value is null");
// TODO: throw exception
} else {
// Get list of sprints this issue has been assigned to
ArrayList<Sprint> sprintList = (ArrayList<Sprint>) fieldValue;

log.debug("The issue has been assigned to the following sprints: ");
for(Sprint sprint : sprintList) {
log.debug("Name = '" + sprint.getName() + "' State = '" + sprint.getState() + "'");
}

// Are any of them active?
boolean anyActiveSprints = Iterables.any(sprintList,
new Predicate<Sprint>() {
@Override
public boolean apply(Sprint sprint) {
return sprint.isActive();
}
});

if (!anyActiveSprints) {
log.debug("None of the assigned sprints are active.");
// TODO: throw exception
}
}
}
}
}

 Sorry for the poor style there - this site is removing all my indentation spaces .

Like georgiossalon likes this
georgiossalon November 20, 2018

Thank you again for replying.

 

How do you even get the Class:

customFieldManager

 ? 

When I import with Gradle the following lib

compile group: 'com.atlassian.jira', name: 'jira-api', version: '7.6.1'

from repository

maven { url'http://repo.spring.io/plugins-release/'}

I get constantly the following error:

jta:jta:1.0.1 FAILED ->Build: ...\Could not find jta:jta:1.0.1. 

I have tried to download only the jta lib and install it, but it still doesnt work.

I also have tried the newer version: 

compile group: 'com.atlassian.jira', name: 'jira-api', version: '7.11.2'

but I still get the same error.

To that I can only write a part of your code without gettint an error like this:

List<CustomField> customFields = ComponentAccessor.getCustomFieldManager().getCustomFieldObjects((com.atlassian.jira.issue.Issue) issue);

so by additionally using the "ComponentAccessor".

 

I do not understand why "JIRA" made the extraction of Data so difficult. It just doesnt make any sense to me.

Tim Eddelbüttel
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 23, 2020

I've also struggeld with fetching Sprint informations like issues not completed in current sprint. After a bit ob searching trouch the APIs I've found that the HistoricSprintDataFactory helps to fetch the Sprint information.

After googling for the Class I've spotted that there is a ScriptRunner example :D
https://scriptrunner.adaptavist.com/6.3.0/jira/listeners.html

TAGS
AUG Leaders

Atlassian Community Events