Getting Tempo billable hours for issue in a ScriptRunner scripted field

Alex Fox February 27, 2020

Has anyone ever achieved this? I'm hoping for some Groovy examples of calculating billable hours for an issue. The one example I found is several years old (https://community.atlassian.com/t5/Jira-questions/Show-tempo-field-quot-Billed-quot-hours-in-jira-issues/qaq-p/71917) and seems to retrieve all logged time, regardless of whether it's billable or not.

Appreciate any insights!

2 answers

2 accepted

1 vote
Answer accepted
Bram V June 5, 2020

Had the same requirement so I did invest some hours to figure out how. The following code allows you to get the attribute from Tempo called BILLABLE_SECONDS. I however noticed that when billable time (which is automatically copied from getTimeSpent during creation of the worklog) is not altered and kept identical, the code below will return a null for BILLABLE_SECONDS. So it seems that this value is not filled during creation if the value is the same as time spent. This is however NOT wat you would expect and something you do not want. Especially since when these values are not the same during creation or are altered and set to the same afterwards, the BILLABLE_SECONDS attribute does get set.

Current workaround that works for me: If the attribute BILLABLE_SECONDS returns null, set it to the same value as getTimeSpent() since this value is probably caused by the bug.

 

import org.apache.log4j.Level
import org.apache.log4j.Logger

def log = Logger.getLogger("com.onresolve.scriptrunner.runner.CLITest")
log.setLevel(Level.DEBUG)

import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.worklog.Worklog;
import com.atlassian.jira.component.ComponentAccessor

import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.tempoplugin.core.workattribute.api.WorkAttributeValueService
import com.tempoplugin.core.workattribute.api.WorkAttributeService

@WithPlugin("is.origo.jira.tempo-plugin")

@PluginModule
WorkAttributeService workAttributeService

@PluginModule
WorkAttributeValueService workAttributeValueService

def issueManager = ComponentAccessor.getIssueManager()
Issue issue = issueManager.getIssueObject(“PROJECTKEY-XXX")

for (Worklog worklog : ComponentAccessor.getWorklogManager().getByIssue(issue)) {
    def attributeBillableSeconds = workAttributeService.getWorkAttributeByKey("BILLABLE_SECONDS").returnedValue
    def workAttributeValue = workAttributeValueService.getWorkAttributeValueByWorklogAndWorkAttribute(worklog.id, attributeBillableSeconds.id)
    def billableseconds = workAttributeValue.returnedValue
    if (billableseconds == null) {
        billableseconds = worklog.getTimeSpent()
    }
    log.debug("Billableseconds: " + billableseconds?.value)
    log.debug("getTimespent: " + worklog.getTimeSpent())
}

Ward Schwillens_ Schwillie June 10, 2020

Hi Bram_V,

 

might be a solution that I can use too. At what point do you run that script then. When someone logs work? 

 

Thank you for your feedback!

Bram V June 10, 2020

This example is something that you can just run at ScriptRunner Console. Personally we have a webhook setup that takes this billable time and syncs it towards out billing system. But you can use it in a lot of scripts, whenever you need to access the billable time.

We also have a scripted field that calculates the total billed time on an Epic for example.

Ward Schwillens_ Schwillie June 11, 2020

Hi Bram_V,

thank you for your reply. Can you share that scripted field script to calculate the total billed time for an epic? Thanks!

jmarko July 1, 2020

Hello Bram_V,

thanks for script. I also need something like this. But I don't know why it isn't working for me. Can you please help me a little. What am I doing wrong? I'm getting errorsatlassian forum.pngAnnotation 2020-07-01 091858.png:

Bram V July 1, 2020

I assume that "IT storitive" does not exist. You have to specify an issue key instead. For example that ITS-33. If issue1 is null, getWorklogManager will throw that error as it cannot get worklogs from nothing (a null object)

Like jmarko likes this
jmarko July 1, 2020

Thank you for comment. Yes, i get this error when I insert issue key... But seems like there is problem in code. Do you know if there is some library missing? 
I'm new to groovy :/

billable min.png

jmarko July 2, 2020

Actually in line
def attributeBillableSeconds = workAttributeService.getWorkAttributeByKey("BILLABLE_SECONDS").returnedValue 
I get null value. Is it possible that BILLABLE_SECONDS is different? Issue ITS-33 exists and has logged time and logged billable time. 
I can call issue.getTimeSpent() and get result.

Bram V July 2, 2020

Did you remove part of the code? Code is there for a reason (most of the time)

My code did mention the following

    if (billableseconds == null) {
        billableseconds = worklog.getTimeSpent()
    }

As you stated, getTimeSpent does show a result. You want to set billableseconds to this value if its empty in the database like my code does. I did provide this feedback to ScriptRunner and Tempo but none of them cares to fix it. That is why you need that workaround.

Apparently if billableseconds is the same as timespent, it will not have the billableseconds key. If billableseconds and timespent are different, or where different once before, that value will be set.

Bram V July 2, 2020

If it complains about the questionmark, just remove it. Will be that you are using some older version probably.

Cristian _Southend_ July 26, 2021

thanks Bram, your code is very useful to me
I would like to know if it is possible to generate a field that updates this value by adding all the billed worklogs
I have tried programming it with your code, but sometimes I get a value that is not numeric and the code throws a runtime error

0 votes
Answer accepted
Alex Fox July 27, 2021

One of our developers solved this eventually, this looks like it may be similar to @Bram V's solution above. This requires Tempo Timesheets and a ScriptRunner Scripted Field.

 

import is.origo.jira.plugin.common.TempoWorklogManager
import com.tempoplugin.core.datetime.api.TempoDateTime
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.worklog.Worklog
import com.atlassian.jira.bc.ServiceOutcome
import com.tempoplugin.core.workattribute.api.WorkAttributeType
import com.tempoplugin.core.workattribute.api.WorkAttributeValue
import com.tempoplugin.core.workattribute.api.WorkAttributeService
import com.tempoplugin.core.workattribute.api.WorkAttributeValueService




@WithPlugin("is.origo.jira.tempo-plugin")




@PluginModule
TempoWorklogManager tempoWorklogManager




WorkAttributeService workAttributeService = tempoWorklogManager.getWorkAttributeService()
WorkAttributeValueService valueService = tempoWorklogManager.getWorkAttributeValueService()





long billableTime = 0
long totalTimeSpent = 0




ComponentAccessor.getWorklogManager().getByIssue(issue).each {Worklog it ->
boolean hasValues = false;

ServiceOutcome so = valueService.getWorkAttributeValuesByWorklogAndType(it.id, WorkAttributeType.Type.BILLABLE_SECONDS)
so.returnedValue.each { WorkAttributeValue value ->
hasValues = true
if (value != null && value.value != null) {
billableTime += Long.parseLong(value.value)
}
}

if (!hasValues) {
billableTime += it.timeSpent
}

totalTimeSpent += it.timeSpent
}




billableTime

 

Cristian _Southend_ July 27, 2021

thanks @Alex Fox, your solution is very effective.
I still detect some inconsistencies, for example, if the worked/billed hours are edited it seems that some relationship is lost and the results are not exact.


Have you ever tried calling the function directly with the Tempo API? I don't know if this can be done, I don't have extensive programming knowledge :)

Thanks a lot for your quick response and help provided

Suggest an answer

Log in or Sign up to answer