Use ScriptRunner Scripted Fields to display a calculated custom field on an issue. Scripted fields allow you to display information otherwise not available for an issue by calculating or amalgamating data from one or more existing fields.

Issues containing a scripted field will not show up in a JQL search until they have been viewed in a browser. This is because running the JQL search does not run the field on an issue as a value trigger.

Currently, scripted fields cannot be selected as columns in search results.

Create a Scripted Field

  1. Navigate to ScriptRunner → Scripted Fields.

  2. Click Create New Scripted Field.

  3. Enter the name of the field in Field Name. NOTE: The Identifier field shows the unique identification key for the new scripted field.

  4. Under Field Status, choose either Enabled or Disabled. When set to Enabled, the scripted field is active (on relevant issues/projects) as soon as it has been saved.

  5. Select the Location the issue view where the new scripted field should appear, either theIssue Content panel or Issue Sidebar. To view scripted fields in the sidebar, click Open Scripted Fields.

    Scripted fields in the issue content may not appear by default when an issue is loaded. To view the scripted fields click the ScriptRunner icon under the issue summary.

  6. Select one or more projects in Project/s. The scripted field only displays on the project/s specified.

  7. Select all Issue Type/s you want the scripted field on

  8. Select the Field Type of your scripted field. Ensure you pick the correct field type for the data returned by your script. See Return Types for more information.

  9. Enter aScript to Execute. This script is triggered when an issue is loaded. See Currency Conversion Number Field for a script example.

    Scripted fields in Jira Cloud do not dynamically update. The script triggers on issue load; therefore, changes to the field value are not reflected instantly, the issue must be reloaded.

  10. Enter an issue key in Test Against Issue to test the scripted field before saving.

  11. ClickSave.

    Any changes to the Scripted Field configuration will require a re-test.

Return Types

The script for your field must return the correct data type.

For a text field, the script must return a single line String (no newline characters).

// This is OK
return "Hello World"
// This is not OK
return "Hello\nWorld"
CODE

For a number field, the script must return a numeric type, for example an Integer, Long, Float, Decimal, or BigDecimal.

For a date field, the script must return a LocalDate.

import java.time.LocalDate
import java.time.Month
return LocalDate.of(2020, Month.JUNE, 25)
CODE

For a datetime field, the script must return a ZonedDateTime.

import java.time.LocalDate
import java.time.LocalTime
import java.time.Month
import java.time.ZonedDateTime
import java.time.ZoneId
import java.time.ZoneOffset
return ZonedDateTime.now(ZoneId.of("America/New_York"))
// or
return ZonedDateTime.of(LocalDate.of(2020, Month.JUNE, 25), LocalTime.of(13, 45, 0), ZoneOffset.ofHours(-3))
CODE

Examples

Currency Conversion Number Field

The following example shows the conversion of a field value, into a specific currency using a publicly available currency conversion API. A Jira custom field (number) called Cost (USD) has already been set up in the target project. The following script takes the Cost (USD) value, converts it to EUR, and displays the result in a scripted field:

// Get the custom field ID for an existing "currency" field
def costField = get("/rest/api/2/field")
        .asObject(List)
        .body
        .find { (it as Map).name == 'Cost (USD)' }
        .id

// Extract the value of that field from the issue being viewed
def usdValue = issue.fields[costField]

// Use a 3rd-party currency conversion REST API
def conversionResult = get("https://api.exchangeratesapi.io/latest?base=USD&symbols=EUR")
        .asObject(Map)
        .body

// Return the new currency value
if (usdValue != null && conversionResult != null) {
    return usdValue * conversionResult.rates.EUR
} else {
    return 0
}
CODE

Last Comment Scripted Field

The following example shows how you can extract the last comment of an issue and to display the value of it inside a script field on the issue sidebar.

This then allows you to easily see what was added to the last comment on the issue without having to scroll through all of the comments on the issue.

This field should be configured to have a Text Field return type and should be configured to display in the Issue sidebar location.

// Get the comments off the issue as a list
def commentsList = issue.fields?.comment?.comments

// Check if the last comment is not null.
if (commentsList) {
    // If comments exists return the last comment and remove any new line characters to make it a valid return type
    return commentsList.last()?.body.toString().replaceAll("\r", " ").replaceAll("\n", " ");
} else {
    // If no comments exist then display some default text.
    return "No comments exist on the issue"
}
CODE

Sum up Story Points Below An Epic Issue Scripted Field

The following example shows how you can return all the Story issues below an Epic issue and to sum up the Story Points field for these and then display this value inside a script field on the Epic issue.

This then allows you to easily see how many story points you have set for all stories inside of your Epic issue.

This field should be configured to display for just the Epic issue type and to have a Number return type as well as to be configured to display in the Issue sidebar location.

If you wish to sum up extra issue types other than Story issues then you can modify the jql queryString parameter in the allStories rest call of the script to include the extra issue types that you require.
// Check if the issue is an Epic issue
if (issue.fields.issuetype.name == "Epic") {

    // Get the field ids
    def fields = get('/rest/api/2/field')
            .asObject(List)
            .body as List<Map>

    // Get the Story Points custom field to use in the script
    def storyPointsField = fields.find { it.name == "Story Points" }?.id

    // Handle if the Story Points Field does not exist
    if (storyPointsField == null) {
        logger.info("Story Points field does not exist ");
        return;
        }

        // Get all issues below the the Epic Issue
        def allStories = get("/rest/agile/1.0/epic/${issue.key}/issue")
        // The JQL query to return all stories, modify this if you wish to return other issue types inside of the epic as well.
                .queryString("jql", "parentEpic =${issue.key} and issuetype = 'Story'")
                .queryString("fields", "parent,$storyPointsField")
                .asObject(Map)
                .body
                .issues as List<Map>

        // Sum the Story Points for all the Story issues returned
        def estimate = allStories.collect { Map story ->
            story.fields[storyPointsField] ?: 0
        }.sum()

        // return the estimate value if it is not null and return 0 if it has no value
        return estimate ?: 0;
    }
CODE