Context:
We have a selfmade Jira plugin that does some trickery regarding remaining estimates and subtasks.
Basically it is a listener for IssueEvent
and IssuePreDeleteEvent
.
- it sets original and remaining estimates of subtasks to
0
if 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
0
if anyIssueEvent
for 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:
-
setEstimate
sets the estimate - a
ModifiedValue
for the change5d -> 0
is put tomodifiedFields
at key"timetracking"
-
setOriginalEstimate
sets the original estimate - a
ModifiedValue
for the change0 -> 0
is put tomodifiedFields
at key"timetracking"
and thus overwrites the previousModifiedValue
- the further processing checks the
modifiedFields
for actual changes and filters out no-ops like the one currently at key"timetracking"
- as now
modifiedFields
is 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 null
or 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 IssueImpl
creates 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 1d
to 4d
as the ModifiedValue
in 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 ModifiedValue
with 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 modifiedValues
with TimeTrackingValue
values instead of Longs
. Actually that is what I’m doing now, I use the IssueService
if 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