We have a selfmade Jira plugin that does some trickery regarding remaining estimates and subtasks.
Basically it is a listener for
- it sets original and remaining estimates of subtasks to
0if someone accidentally sets those values
- it increases or decreases the remaining estimate of the parent issue if work is logged, changed or deleted on a subtask or a subtask is deleted entirely
- it sets remaining estimate to
IssueEventfor a parent issue is dispatched and the parent issue has a resolution
This used to work fine in a former version of Jira, but I cannot say in which version it last worked.
We found, that in the version
7.13.8 we currently use, the plugin does not work cleanly and I found that the behavior is pretty strange regarding updating the timetracking estimates in mutliple extends and as far as I can tell this also did not change in Jira 8.
As this can be considered a system action and not a user action, I’m not using the
IssueService which does checks on whether the fields are on the current screen, whether the user has the right to change it and so on, but the
IssueManager which allows to change the values regardless. Also
IssueService does not allow to update the timetracking information once the issue is in closed state, but if the issue is directly closed, the remaining estimate should also be set to
0, so using
IssueService is not really an option, besides that it also does not work properly.
The code that should update the value is basically like this:
MutableIssue mutableIssue = issueManager.getIssueObject(issueEvent.getIssue().getId()); mutableIssue.setEstimate(0L); // this for example for setting to 0 for resolved issues mutableIssue.setEstimate(null); // this and the next for example for forcing the subtask estimates to unset mutableIssue.setOriginalEstimate(0L); issueManager.updateIssue(issueEvent.getUser(), mutableIssue, UpdateIssueRequest.builder().build());
The first issue I found was, that
IssueImpl which is the type of the object returned by
getIssueObject stores the
ModifiedValue for both fields in the
modifiedFields map under the key
"timetracking". That means, if the estimates are currently
0 for original and
5d for remaining, what happens is the following:
setEstimatesets the estimate
ModifiedValuefor the change
5d -> 0is put to
setOriginalEstimatesets the original estimate
ModifiedValuefor the change
0 -> 0is put to
"timetracking"and thus overwrites the previous
- the further processing checks the
modifiedFieldsfor actual changes and filters out no-ops like the one currently at key
- as now
modifiedFieldsis empty, no update is done at all as the logic thinks there was no change which is wrong
While this is wrong and pretty confusing behavior, it was easy to work-around. I added two
if statements that only call the respective method if the current value is not
0, then the updates worked fine again in that case.
The next issue I found (and I think it is strongly related to the last issue) was, that
TimeTrackingSystemField#updateValue which is called by
DefaultIssueManager#updateFieldValues which is called by
DefaultIssueManager#updateIssue - the method I call from my code - looks for
ModifiedValue#getNewValue and checks its type. If the type is not
TimeTrackingSystemField.TimeTrackingValue, it treats the the change for the history as being in legacy mode instead of modern mode, which is not the case for our configuration. The problem here seems to be that
ModifiedValue instances with
Long values, regardless whether legacy mode or modern mode is used. The effect of this is, that the history always shows both values to be modified, according to the last
ModifiedValue put to
modifiedFields. So if you have currently original estimate
1d and remaining estimate
2d and then use above describe method to set remaining estimate to
3d and original estimate to
4d, then the result is correct, but the history states that both estimates were changed from
4d as the
modifiedFields is considered legacy mode and so meant for both fields. This only affects the history as far as I can tell. But I guess the correct fix would be to create a
TimeTrackingSystemField.TimeTrackingValue as value in
IssueImpl if modern mode is used. This - if done properly - would probably also fix the last issue, as then the original and remaining estimates can be combined in the one
"timetracking" field in
modifiedFields instead of one overwriting the other.
I have no work-around for this one, other than either using the
IssueService instead of the
IssueManager which properly uses the modern mode but is not really an option as detailed above, or depending on
jira-core and manually adjusting the
TimeTrackingValue values instead of
Longs. Actually that is what I’m doing now, I use the
validateUpdate was successful and if not, then I use the
IssueManager and manually correct the
modifiedFields so that
IssueImpl then behaves properly.
The most notable and critical issue for me though is, that the updating of the estimates does not work while processing an
IssueEvent happening due to a workflow transition. No matter whether I use the
IssueService or the
IssueManager, if I’m inside my listener triggered by a workflow transition like the issue being resolved or closed, the change gets written to the history with the quirks described above, but the values are not updated but remain like they are. For other
IssueEvents like changing the assignee while the issue has a resolution or writing a comment while the issue has a resolution or changing the estimates while the issue has a resolution, the updating of the estimates works fine and the remaining estimate is set to
0 as expected.
I found a work-around for this too by listening for
IssueChangedEvent additionally and doing the remaining estimate setting to
0 for resolved issues there too, as from that handler it works as expected with the quirks described above.
So these are basically three issues I’m reporting for which I have more or less work-arounds. But maybe you can tell me a better way to update the estimate values that does not suffer from the three problems described above.
Further, interestingly the wired test didn’t find the last of the problems described above, while a
jira-testkit test that does the exact same actions, just via testkit instead of plugin Java API did find the problem, so the wired test somehow got stale or not-fully persisted information which might actually be a fourth problem.
The wired test basically does it like:
MutableIssue parentIssue = issueManager.getIssueObject(parentKey); parentIssue.setResolutionObject(resolutionManager.getDefaultResolution()); issueManager.updateIssue(adminUser, parentIssue, UpdateIssueRequest.builder().build()); parentIssue = issueManager.getIssueObject(parentKey); assert parentIssue.getEstimate() == 0
while the testkit test does it like:
IssuesControl issues = new Backdoor(new TestKitLocalEnvironmentData()).issues().loginAs("tester"); issues.transitionIssue(parentKey, transitionId); assertNotNull("resolution is null", issues.getIssue(parentKey).fields.resolution); assert Optional.ofNullable(issues.getIssue(parentKey).fields.timetracking).map(tt -> tt.remainingEstimateSeconds).orElse(0L).longValue() == 0