This page gives some guidance on how to use ScriptRunner to work with Tempo Timesheets for JIRA, to accomplish some basic scripting requirements, such as:

The cornerstone of this is the two annotations discussed in Scripting Other Plugins, namely @WithPlugin and @PluginModule .

Let’s dive straight into the examples.

These examples use Tempo Timesheets for JIRA version 8. For information that works in version 7, see previous versions of this page.

Examples

Summing worklogs with an attribute

Tempo allows you to categorise worklogs with additional attributes. Let’s say we have a checkbox attribute called Overtime, and we’d like to display the total overtime on each issue.

We configure the Overtime attribute, as in the following example:

attributes

Set up a custom scripted field. In this example I’m going to call it Total Overtime. Make sure to use the Duration Searcher as the Search template.

The custom field script is

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.worklog.Worklog
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.tempoplugin.core.workattribute.api.WorkAttributeService
import com.tempoplugin.core.workattribute.api.WorkAttributeValueService

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

@PluginModule
WorkAttributeService workAttributeService

@PluginModule
WorkAttributeValueService workAttributeValueService

def worklogManager = ComponentAccessor.getWorklogManager()

def worklogs = worklogManager.getByIssue(issue)

def overtimeLogs = worklogs.findAll { worklog ->
    def attribute = workAttributeService.getWorkAttributeByKey("_Overtime_").returnedValue
    workAttributeValueService.getWorkAttributeValueByWorklogAndWorkAttribute(worklog.id, attribute.id).returnedValue
}

overtimeLogs.sum { Worklog worklog ->
    worklog.timeSpent
} as Long
// if no overtime worklogs just return null

Use a custom template so that the duration is presented nicely, but note that we return a Long value so the duration searcher can index it properly:

Custom Template
#if ($value)
    $jiraDurationUtils.getFormattedDuration($value)
#end

Template

Custom

Searcher

Duration Searcher

If configured properly we should be able to search for issues where the overtime has exceeded 4 hours using: "Total Overtime" > 4h.

Automatically adding a worklog

In this example we will automatically create a worklog when an issue is transitioned out of some state. Let’s imagine that in our workflow all issues raised by customers go through a Triage state. When the triage is complete we want to automatically log the amount of time it took us to triage the issue. Whether that’s billable or not is not in scope of this example!

This is a script workflow post-function, the code is:

import com.atlassian.jira.component.ComponentAccessor
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.tempoplugin.core.workattribute.api.WorkAttributeService
import is.origo.jira.plugin.common.TempoWorklogManager
import com.tempoplugin.core.datetime.api.TempoDateTime


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

@PluginModule
TempoWorklogManager tempoWorklogManager
@PluginModule
WorkAttributeService workAttributeService

def currentUser = ComponentAccessor.getJiraAuthenticationContext().getUser()
def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()

// calculate the time we entered this state
def changeHistoryItems = changeHistoryManager.getAllChangeItems(issue).reverse()
def timeLastStartProgress = changeHistoryItems.find {
    it.field == "status" && it.toValues.values().contains("In Progress")
}?.created

def triageType = workAttributeService.getWorkAttributeTypes().returnedValue.find{it.name == "_WorkType_"}
def attributes =  workAttributeService.getWorkAttributesByType(triageType).returnedValue // set the Tempo Worktype attribute

def timeInProgress = (new Date()).time - timeLastStartProgress.time // this is in millis
def timeToLog = Math.round(timeInProgress.longValue() / 1000) // so round to seconds
def remaining = issue.estimate ? (issue.estimate - timeToLog) : 0 // calc remaining estimate

tempoWorklogManager.create(issue.key, TempoDateTime.now(), "Auto-created worklog for triage",
        attributes,
        timeToLog, 0, remaining,
        currentUser, [:])

So, when we execute the action that takes us out of the In Triage state, e.g. Triage Complete, a worklog is added, and the Tempo drop-down called Work type is set to Triage.

You could query on issues that have had X hours logged to the Triage work type using a similar script field as above.

Population of the dropdown

For its Dynamic Drop Downs feature, Tempo requires the keys and values for the drop-down in JSONP format. We can provide this easily using a script REST endpoint. Note you can customise the available options based on the query parameters.

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.transform.BaseScript

import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

tempoWorkType(httpMethod: "GET") { MultivaluedMap queryParams, String body ->

    def callbackFn = queryParams.getFirst("callback")

    def options = [
        values: [
            [
                key:"",
                value:"Please select..."
            ],
            [
                key:"TRIAGE",
                value:"Triage"
            ],
            [
                key:"DEV",
                value:"Development"
            ],
            [
                key:"QA",
                value:"Quality Assurance"
            ]
        ]
    ]

    def resp = """${callbackFn} ( ${new JsonBuilder(options).toPrettyString()} )""".toString()

    return Response.ok(resp).build()
}

You could then inspect the output at <jira_base_url>/rest/scriptrunner/latest/custom/tempoWorkType. Note that Tempo will automatically supply the callback function name.

For how-to questions please ask on Atlassian Answers where there is a very active community. Adaptavist staff are also likely to respond there.

Ask a question about ScriptRunner for JIRA, for for Bitbucket Server, or for Confluence.