Examples

Calculate a number based on other fields

Eg let’s say we have a number field called Severity, and we want some new value that is the product of the priority and severity:

def severity = getCustomFieldValue("Severity")
if (severity) {
    return severity * Integer.parseInt(issue.priority.id)
}
else {
    return null
}
GROOVY

Note that I’m careful to test this against an issue that has no Severity value set.

Total time this issue has been In Progress

This will show the duration that an issue has been in the In Progress state - summing up multiple times if necessary.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.history.ChangeItemBean

def changeHistoryManager = ComponentAccessor.getChangeHistoryManager()

def inProgressName = "In Progress"

List<Long> rt = [0L]
def changeItems = changeHistoryManager.getChangeItemsForField(issue, "status")
changeItems.reverse().each { ChangeItemBean item ->
    def timeDiff = System.currentTimeMillis() - item.created.getTime()
    if (item.fromString == inProgressName) {
        rt << -timeDiff
    }
    if (item.toString == inProgressName) {
        rt << timeDiff
    }
}

def total = rt.sum() as Long
return (total / 1000) as long ?: 0L

Use the Duration Searcher. For the template choose Duration.

Number of attachments an issue has

This example is superseded by the JQL function hasAttachments.

A simple scripted field to show the number of attachments. This could easily be modified to show the number with a specific extension etc.

import com.atlassian.jira.component.ComponentAccessor

def attachmentManager = ComponentAccessor.getAttachmentManager()
def numberAttachments = attachmentManager.getAttachments(issue).size()

// use the following instead for number of PDFs
//def numberAttachments = attachmentManager.getAttachments(issue).findAll {a ->
//    a.filename.toLowerCase().endsWith(".pdf")
//}.size()

return numberAttachments ? numberAttachments as Double : null
GROOVY

Use the Number Searcher with the Number template.

All Previous Versions

This sample script field displays every version in a given project prior to the oldest fix version.

package com.onresolve.jira.groovy.test.scriptfields.scripts

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.comparator.VersionComparator

def versionManager = ComponentAccessor.getVersionManager()
def versions = versionManager.getVersions(issue.projectObject)
def comparator = new VersionComparator()
def lowestFixVersion = issue.fixVersions.min(comparator)
def returnedVersions = versions.findAll {
    comparator.compare(it, lowestFixVersion) < 0
}
log.debug("All prior versions: ${returnedVersions}")
return (lowestFixVersion ? returnedVersions : null)
GROOVY

The script returns a list of Version objects.

Template

Version

Searcher

Version Searcher

Information Message

Sometimes you might just want to display some extra information about the issue, and not be able to search it. If you like, your script can output HTML, and you effectively don’t use a velocity template. This is similar to the now obsolete "velocity-processed custom field for view", but on steroids.

In this case use the HTML template option. There’s no point in enabling a searcher as there is not one that can handle HTML.

As an example, let’s say we want to draw attention to an issue when it is blocked by other issues that are not resolved, and not assigned. (Or not scheduled, not reviewed, whatever).

The field on the screen will look like this:

In the code below I use a MarkupBuilder, although feel free to just construct a string containing html and return that. As usual return null if there are no blocking issues, so the field is not displayed at all.

package com.onresolve.jira.groovy.test.scriptfields.scripts

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import groovy.xml.MarkupBuilder
import com.atlassian.jira.config.properties.APKeys

def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL)

def List<Issue> blockingIssues = []

issueLinkManager.getInwardLinks(issue.id).each { issueLink ->
    if (issueLink.issueLinkType.name == "Blocks") {
        def linkedIssue = issueLink.sourceObject
        if (!linkedIssue.assigneeId && !linkedIssue.resolutionObject) {
            blockingIssues.add(linkedIssue)
        }
    }
}

if (blockingIssues) {
    StringWriter writer = new StringWriter()
    MarkupBuilder builder = new MarkupBuilder(writer)

    builder.div(class: "aui-message aui-message-error shadowed") {
        p(class: "title") {
            strong("This issue is blocked by the following unresolved, unassigned issue(s):")
        }

        ul {
            blockingIssues.each { anIssue ->
                li {
                    a(href: "$baseUrl/browse/${anIssue.key}", anIssue.key)
                    i(": ${anIssue.summary} (${anIssue.statusObject.name})")
                }
            }
        }
    }

    return writer
} else {
    return null
}
GROOVY

Template

HTML

Searcher

None

Show Component Lead

Displays the lead for the component selected for this issue, making use of the User display template.

Script is:

package com.onresolve.jira.groovy.test.scriptfields.scripts

def components = issue.componentObjects.toList()
if (components) {
    return components?.first()?.componentLead
}
GROOVY

We return a User object, and use the User Picker template so the user is displayed with a clickable link and mouseover popup etc.

Make sure you return an ApplicationUser object and not a User. If you do the latter the template will show Anonymous.

Template

User Picker (single user)

Searcher

User Picker Searcher

Show Multiple Users

To show all unique component leads you would use this script:

package com.onresolve.jira.groovy.test.scriptfields.scripts

def componentLeads = issue.componentObjects*.componentLead.unique()
componentLeads.removeAll([null])
componentLeads
GROOVY

Template

User Picker (multiple users)

Searcher

Multi User Picker Searcher

This will look like:

Don’t forget to reindex if you want your newly created scripted custom field to show up in existing issues.

Work Remaining in Linked Issues

Show the work remaining in all issues of the Composition link types

Sometimes you want to use the "Composition" or "part of" link types to break down large tasks rather than subtasks. However you can’t see the rolled up remaining estimate. This custom field shows the amount of time remaining on all issues that this issue comprises. The outbound link is Comprises and the inbound link would be is comprised of.

package com.onresolve.jira.groovy.test.scriptfields.scripts

import com.atlassian.jira.component.ComponentAccessor

def issueLinkManager = ComponentAccessor.getIssueLinkManager()

def totalRemaining = 0
issueLinkManager.getOutwardLinks(issue.id).each { issueLink ->
    if (issueLink.issueLinkType.name == "Composition") { 
        def linkedIssue = issueLink.destinationObject
        totalRemaining += linkedIssue.getEstimate() ?: 0
    }
}

// add the remaining work for this issue if there are children, otherwise it's just the same as the remaining estimate,
// so we won't display it,
if (totalRemaining) {
    totalRemaining += issue.getEstimate() ?: 0
}

return totalRemaining as Long ?: 0L
GROOVY

Line 9: Change the link type name to suit

The important thing is that this returns a Long, so that we can search on it, however that’s not very useful to display to users.

Select the Duration (time-tracking) template to make the display of the custom field match that of the Estimate field.

Template

Duration (time-tracking)

Searcher

Duration Searcher

You should also be able to search on this scripted field using the same syntax you can for other time tracking fields:

Indexing

You may notice a further problem here. When work is logged on one of the linked issues, the issues that link to it don’t get reindexed, so a search won’t return the correct results.

To handle this, we use a custom Script Listener to follow "is comprised of" links and reindex those issues:

package com.onresolve.jira.groovy.test.scriptfields.scripts

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.index.IssueIndexManager

def issue = event.issue // event is an IssueEvent
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def issueIndexManager = ComponentAccessor.getComponent(IssueIndexManager)

issueLinkManager.getInwardLinks(issue.id).each { issueLink ->
    if (issueLink.issueLinkType.name == "Composition") { 
        def linkedIssue = issueLink.getSourceObject()
        issueIndexManager.reIndexIssueObjects([linkedIssue])
    }
}
GROOVY

Line 11: Change the link type name to suit

This is configured as so:

Further Examples