We are using the REST API to retrieve some information from JIRA for reporting purposes. Basically, we are getting the number of reported hours per project/project category in a specific time period.
Recently we have noticed that the numbers of reported hours are not correct i.e. that those are smaller than the real numbers in JIRA. The analysis has shown that the getWorklogs method called on an issue is returning only up to 20 worklogs. This looks like some kind of pagination, but we were not able to find this limitation anywhere in the documentation.
Can somebody please shed some light on this?
We had the same problem.
The restAPI (/rest/api/2/issue/{issueIdOrKey}) that is behind de IssueRestClient contains max. 20 worklogs per issue.
Te same for the SearchRestClient: the restAPI (/rest/api/2/search) also returns max. 20 worklogs per issue.
The only restAPI, that returns all the worklogs, is this one: /rest/api/2/issue/{issueIdOrKey}/worklog. Unfortunately, there is no corresponding Java RestClient in JRJC.
After looking into the JRJC code, I created some new classes, based on the existing code.
First I created a new IssueWorklogsRestClient interface with its class. There is one method getIssueWorklogs(BasicIssue issue) that returns a List of Worklogs per issue:
public interface IssueWorklogsRestClient { Promise<List<Worklog>> getIssueWorklogs(BasicIssue issue); }
public class AsynchronousIssueWorklogsRestClient extends AbstractAsynchronousRestClient implements IssueWorklogsRestClient { private final WorklogsJsonParser worklogsParser = new WorklogsJsonParser(); private final URI baseUri; public AsynchronousIssueWorklogsRestClient(final URI baseUri, final HttpClient client) { super(client); this.baseUri = baseUri; } @Override public Promise<List<Worklog>> getIssueWorklogs(final BasicIssue issue) { final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri); uriBuilder.path("issue").path(issue.getKey()).path("worklog"); worklogsParser.setIssue(issue); return getAndParse(uriBuilder.build(), worklogsParser); } }
I also created a new WorklogsJsonParser, based on the existing IssueJsonParser
public class WorklogsJsonParser implements JsonObjectParser<List<Worklog>> { private BasicIssue basicIssue; public void setIssue(BasicIssue issue) { this.basicIssue = issue; } @Nullable private <T> Collection<T> parseOptionalArray(final JSONObject json, final JsonWeakParser<T> jsonParser, final String... path) throws JSONException { final JSONArray jsonArray = JsonParseUtil.getNestedOptionalArray(json, path); if (jsonArray == null) { return null; } final Collection<T> res = new ArrayList<T>(jsonArray.length()); for (int i = 0; i < jsonArray.length(); i++) { res.add(jsonParser.parse(jsonArray.get(i))); } return res; } @Override public List<Worklog> parse(JSONObject s) throws JSONException { final Collection<Worklog> worklogs; final URI selfUri = basicIssue.getSelf(); worklogs = parseOptionalArray(s, new JsonWeakParserForJsonObject<Worklog>(new WorklogJsonParserV5(selfUri)), WORKLOGS_FIELD.id); if(worklogs==null) { return Collections.<Worklog>emptyList(); } return new ArrayList(worklogs); } private static class JsonWeakParserForJsonObject<T> implements JsonWeakParser<T> { private final JsonObjectParser<T> jsonParser; public JsonWeakParserForJsonObject(JsonObjectParser<T> jsonParser) { this.jsonParser = jsonParser; } private <T> T convert(Object o, Class<T> clazz) throws JSONException { try { return clazz.cast(o); } catch (ClassCastException e) { throw new JSONException("Expected [" + clazz.getSimpleName() + "], but found [" + o.getClass().getSimpleName() + "]"); } } @Override public T parse(Object o) throws JSONException { return jsonParser.parse(convert(o, JSONObject.class)); } } private interface JsonWeakParser<T> { T parse(Object o) throws JSONException; } }
finally I created a subclass+interface of the existing JiraRestClient that creates the new IssueWorklogsClient
public interface JiraRestClientPlus extends JiraRestClient { IssueWorklogsRestClient getIssueWorklogRestClient(); }
public class AsynchronousJiraRestClientPlus extends AsynchronousJiraRestClient implements JiraRestClientPlus { private final IssueWorklogsRestClient issueWorklogsRestClient; public AsynchronousJiraRestClientPlus(final URI serverUri, final DisposableHttpClient httpClient) { super(serverUri,httpClient); final URI baseUri = UriBuilder.fromUri(serverUri).path("/rest/api/latest").build(); // '/rest/api/latest' or '/rest/api/2' issueWorklogsRestClient = new AsynchronousIssueWorklogsRestClient(baseUri, httpClient); } @Override public IssueWorklogsRestClient getIssueWorklogRestClient() { return issueWorklogsRestClient; } }
+ a subclass of the existing AsynchronousJiraRestClientFactory to create the new JiraRestClientPlus
public class AsynchronousJiraRestClientFactoryPlus extends AsynchronousJiraRestClientFactory { @Override public JiraRestClientPlus create(final URI serverUri, final AuthenticationHandler authenticationHandler) { final DisposableHttpClient httpClient = new AsynchronousHttpClientFactory() .createClient(serverUri, authenticationHandler); return new AsynchronousJiraRestClientPlus(serverUri, httpClient); } @Override public JiraRestClientPlus createWithBasicHttpAuthentication(final URI serverUri, final String username, final String password) { return create(serverUri, new BasicHttpAuthenticationHandler(username, password)); } @Override public JiraRestClientPlus create(final URI serverUri, final HttpClient httpClient) { final DisposableHttpClient disposableHttpClient = new AsynchronousHttpClientFactory().createClient(httpClient); return new AsynchronousJiraRestClientPlus(serverUri, disposableHttpClient); } }
For the rest, I use my existing code, but added a extra check that when worklogs.size >=20, it will get the worklogs by using the new IssueWorklogsRestClient.
If you ever try to make a copy of this great workaround you might stumble over line 32 of WorklogsJsonParser
: The reference to WORKLOGS_FIELD
is missing.
Replace it with com.atlassian.jira.rest.client.api.domain.IssueFieldId.WORKLOGS_FIELD.id
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
This issue is being fixed in: https://jira.atlassian.com/browse/JRA-34746
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Do we know if this is an issue on JIRA 7?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
It's "being fixed" for several years now
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
The original bug ticket has been splitted in 2, one for Server and one for Cloud. As you can see the one for Cloud is already fixed since the limit has been added on purpose as you can read in:
We are sorry there was change in a behaviour. However as it was made due to performance reasons we want to keep it as it is.
If anyone is still willing to get full list of worklog items I would recommend using GET /rest/api/2/issue/{issueIdOrKey}/worklog]
If you have a look at the REST API documentation you can check that indeed there are new worklog related endpoints (per issue, per time interval, etc):
As regarding Jira Server, I am not even sure that issue has ever been there. So maybe it is just there open by mistake.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
I'm using Jira Server, the issue is there.
Problem is, Atlassian tries to decide for the end-users what's best for them: We are sorry there was change in a behaviour. However as it was made due to performance reasons we want to keep it as it is.
If there are any performance reasons (and I'm pretty sure there are!) with querying issues containing tonnes of worklogs then by all means warn me BUT let me decide if it's acceptable in my particular scenario.
In some cases it would be totally ok if Jira took its time and returned all changed worklogs in one giant and neat response, albeit after a week of processing (ok, we all understand it will take several minutes tops).
And I'm forced to call several consequent endpoints and create a process of parsing additional data, looping etc, though a perfectly working architecture for providing all the data I need is already there
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
We will go with the Tempo API instead since it provides the data we need.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
https://docs.atlassian.com/jira/REST/latest/#idp1939456
returns a collection of worklogs associated with the issue, with count and pagination information
I don't know if this will work but base on maxResults, you might have to do recursive calls with the startAt parameter added.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Thanks. Recursive calls would be fine, but I am not sure that I see a way to do it using only the Java API. We have something like this :
Issue issue = restClient.getIssueClient().getIssue(key).claim(); Iterable<Worklog> worklogs = issue.getWorklogs();
And there is no way, or at least I do not see it, to set the startAt or max Results parameters.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Sorry. I don't use the rest client. I make my own REST calls. I'm sure the REST client does not expose the other results. Tough luck.
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.