Hi all,
I want to create a few scripted fields that essentially act in the same way as the SLAs fields from Jira Service Desk, but for use in our Jira Software instance to calculate the time that an issue has been in a certain collection of statuses (within context of its pertaining workflow) from the time of its creation all the way to its resolution.
I want to create a few fields that calculate time in this manner:
So at first, I figured that my best bet was to leverage the Count the time an issue was in a particular status code from Adaptavist Library, which you can use in a scripted field template to record the time an issue has been in a specific status.
The big thing that I want to do, which will hopefully help me create the fields listed above, is change this code up so that it can calculate the time in not just one status, but rather a list of statuses...or maybe all statuses in a certain status category (that pertains to the context of the workflow), like the To Do statuses.
Here's how I modified the library code snippet so far in an attempt to create the TO DO statuses field:
import com.atlassian.core.util.DateUtils
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.history.ChangeItemBean
def changeHistoryManager = ComponentAccessor.changeHistoryManager
// Status to be counted
def toDo = 'To Do'
def totalStatusTime = [0L]
// Every status change is checked
def changeItems = changeHistoryManager.getChangeItemsForField(issue, "status")
changeItems.reverse().each { ChangeItemBean item ->
def timeDiff = System.currentTimeMillis() - item.created.time
// Subtract time if the "from" status is equal to the status to be checked and from and to statuses are different.
// This allows to count the time the issue is in the state for the first time
if (item.fromString == toDo && item.fromString != item.toString) {
totalStatusTime << -timeDiff
}
// Add time if the "to" status is equal to the status to be checked
if (item.toString == toDo) {
totalStatusTime << timeDiff
}
}
def intakeReview = "Intake"
def totalStatusTime2 = [0L]
changeHistoryManager.getChangeItemsForField (issue, "status")
changeItems.reverse().each {ChangeItemBean item ->
def timeDiff = System.currentTimeMillis() - item.created.time
if (item.fromString == intakeReview && item.fromString != item.toString) {
totalStatusTime << -timeDiff
}
if (item.toString == intakeReview) {
totalStatusTime << timeDiff
}
}
// Combining the time values in both arrays to get total time in both statuses
totalStatusTime.addAll(totalStatusTime2)
def total = totalStatusTime.sum() as Long
// Every time (added or subtracted) is summed and divided by 1000 to get seconds
(total / 1000) as long ?: 0L
As you can see, this was basically just me copying and pasting the changeItems code block and handling the Intake status in addition to To Do.
Previewing this code with a test issue in our instance (TEST-1518), I'm having two major problems with this approach:
1.) To Do is the first status in the workflow for TEST-1518. It was in the To Do status for 4 days, 18 hours, 53 minutes. Then it was transitioned to Intake and has been in that status for 35 weeks, 4 days, 6 hours, 57 minutes. I expected my code to add the two times together, where the calculated result would be: [4 days, 18 hours, and 53 minutes] + [35 weeks, 4 days, 6 hours, and 57 minutes] = [36 weeks, 2 days, 1 hour, 50 minutes]. Unfortunately, I'm only getting null in the Result Log.
If I were to replace To Do with a status like In Progress, then it would add to the time in Intake and the time total would calculate as expected...but not for the first status in the workflow for some reason. Even if I used the base script from Adaptavist and tested it solely with time in the To Do status, it'll still return null! I'm assuming it was because there were no change history items to be found from anything past the issue being in To Do?
How do I fix this?
2.) Even if I had no problems with my changes to the script, I feel like I'm not doing this efficiently. I know that pasting a bunch of copies of the changeItems code block to account for different statuses wouldn't be efficient. To anyone out there...is there a way I could make the library code snippet work for more than one status? So instead of calculating the time in one status, I could do it for a string array of statuses like:
// Statuses to be counted
def statuses = ['New', 'To Do', 'Scope', 'Backlog', 'Pending Assignment', 'Intake', 'Selected for Development']
.
.
.
Or maybe reference all the To Do type statuses (and block out certain named statuses like On Hold, Blocked, Stalled)...whichever makes more sense. If you have an approach to the calculated fields that is better than what I have in mind, I'm all ears.
Thanks in advance to anyone that might have insight on my question post! Any tips/suggestions/corrections to the code would be invaluable to me. Any more info you need from me, let me know.
Hi Ian,
Before you jump deeper into it, you have to understand how Jira's issue history works a.k.a. Change History.
The ChangeItemBean object itself contains fromString and toString, which is equivalent to what you see from the UI here:
With that said, let's get to your questions:
(total / 1000) as long ?: 0LSimply add return to specify which to return. Alternatively, check my next reply item for a more scalable return object using Map [:].
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.history.ChangeItemBean
def statuses = ['To Do', 'In Progress', 'Done']
def time = [:]
statuses.each{
// Status to be counted
def statusName = it
def changeHistoryManager = ComponentAccessor.changeHistoryManager
def totalStatusTime = [0L]
// Every status change is checked
def changeItems = changeHistoryManager.getChangeItemsForField(issue, "status")
changeItems.reverse().each { ChangeItemBean item ->
def timeDiff = System.currentTimeMillis() - item.created.time
// Subtract time if the "from" status is equal to the status to be checked and from and to statuses are different.
// This allows to count the time the issue is in the state for the first time
if (item.fromString == statusName && item.fromString != item.toString) {
totalStatusTime << -timeDiff
}
// Add time if the "to" status is equal to the status to be checked
if (item.toString == statusName) {
totalStatusTime << timeDiff
}
}
def total = totalStatusTime.sum() as Long
// Every time (added or subtracted) is summed and divided by 1000 to get seconds
time[statusName] = (total / 1000) as long ?: 0L
}
return time
[To Do:-814, In Progress:814, Done:0]
I hope this helps!
Regards
Thanks for the reply! And sorry for my late reply, was absent from my computer for the past few days.
So I tried incorporating the loop you suggested, and here's the updated script:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.history.ChangeItemBean
def statuses = ['Open','New','Scope', 'To Do', 'Backlogged', 'Backlog', 'Intake Review', 'Intake', 'Selected for Development', 'Ready']
def time = [:]
statuses.each {
def statusName = it
def changeHistoryManager = ComponentAccessor.changeHistoryManager
def totalStatusTime = [0L]
def changeItems = changeHistoryManager.getChangeItemsForField(issue, "status")
changeItems.reverse().each {
ChangeItemBean item ->
def timeDiff = System.currentTimeMillis() - item.created.time
if (item.fromString == statusName && item.fromString != item.toString) {
totalStatusTime << -timeDiff
}
if (item.toString == statusName) {
totalStatusTime << timeDiff
}
}
def total = totalStatusTime.sum() as Long
time[statusName] = (total / 1000) as long ?: 0L
}
return time
I previewed over an issue that has TO DO Statuses called 'New', 'Intake Review', 'Backlogged', and 'Ready'.
When I set the return template as "Text Field (multi-line)", I get the following string:
[Open:0, New:-1104, Scope:0, To Do:0, Backlogged:495, Backlog:0, Intake Review:532, Intake:0, Selected for Development:0, Ready:75]
So I have a few followup questions with this new outcome in place:
1.) Why does the output for the first status come out as a negative number? Is there a way to have it normally display the time difference in between that and the next status it transitions to?
2.) Is there a way to take the number of seconds for each status, add them together, and display the result using Duration as the template for the result? Because the big thing I want to get out of these fields as result is the total time spent in all of the statuses mentioned in the statuses array. When I ran your version of the code using the Duration template rather than the Text Field (multi-line) template, unfortunately the result didn't come out as expected. I got this error message instead:
An error occurred whilst rendering this message.
Please contact the administrators, and inform them of this bug.
Details: ------- org.apache.velocity.exception.MethodInvocationException:
Invocation of method 'formatDurationPretty' in class com.atlassian.core.util.DateUtils threw exception
java.lang.NumberFormatException: For input string: "[Open:0, New:-931, Scope:0, To Do:0, Backlogged:323,
Backlog:0, Intake Review:532, Intake:0, Selected for Development:0, Ready:75]" at templates/customfield/view-duration.vm[line 3, column 16]
at org.apache.velocity.runtime.parser.node.ASTMethod.handleInvocationException(ASTMethod.java:342) at
org.apache.velocity.runtime.parser.node.ASTMethod.execute(ASTMethod.java:284) at org.apache.velocity.runtime.parser.node.ASTReference.execute(ASTReference.java:262)
at org.apache.velocity.runtime.parser.node.ASTReference.render(ASTReference.java:342) at org.apache.velocity.runtime.parser.node.ASTBlock.render(ASTBlock.java:72)
at org.apache.velocity.runtime.parser.node.ASTIfStatement.render(ASTIfStatement.java:87) at org.apache.velocity.runtime.parser.node.SimpleNode.render(SimpleNode.java:336) at org.apache.velocity.Template.merge(Template.java:328) at org.apache.velocity.Template.merge(Template.java:235) at org.apache.velocity.app.VelocityEngine.mergeTemplate(VelocityEngine.java:381).....
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.