Administration Functions

Escalation Service

Discussed in its own page, see Escalation Service.

Switch User

The Switch User built-in script allows administrator users to temporarily assume the identity of another user. Switch User has a variety of uses, such as:

  • Reproducing and troubleshooting problems specific to a user to diagnose permissions issues.

  • Updating an issue on behalf of another user if they are unavailable.

To switch back to the original user, log out and log in again.
  1. From ScriptRunner, navigate to Built-in Scripts→Switch to a Different User.

  2. Under User, type the name of the target user.

  3. Click Preview to see if the user switch is valid.

    switch user
    You cannot switch to a user with a higher permission level. For example, a project administrator cannot switch to a system administrator.
  4. If valid, click Run. Then click here under results.

    switch user run

When a user assumes the identity of another user, it is recorded in the Audit Log as follows:

switch user audit

To prevent the impersonation of system administrator users by other system administrators enable the following dark feature:

scriptrunner.canned.jira.admin.switchuser.denyimpersonatesysadmin

Click here for more information on dark features.

Change Dashboard or Filter Ownership

Use the Change Dashboard or Filter Ownership built-in script to change the owner for one or more dashboards or filters from one user to another.

If, for example, an employee has left your company, an administrator could use this script to transfer all dashboard and filter ownership at once before deactivating the user.

Dashboards comprise gadgets or portlets which use filters. When dashboard ownership changes, any filters within the dashboard will error; therefore the filter ownership needs to be changed as well. The Change Dashboard or Filter Ownership built-in script changes ownership of any filters contained within the effected dashboard using the following rules:

  • If the dashboard being modified includes filters with the same owner, the filter owner is also modified.

  • If a different user owns the filter and the target user has permission to view the filter, no change is made.

  • If a different user owns the filter and the target user does not have permission to view the filter, the filter is shared globally.

    1. From ScriptRunner, navigate to Built-in Scripts→Change Dashboard or Filter Ownership.

    2. In From User, select the original owner of the dashboard or filter.

    3. In To User, select the username of the new owner of the dashboard or filter.

    4. In the Dashboards field, all dashboards owned by the user specified in From User are listed. Select the name of the dashboards for which ownership will change to the user specified in To User.

      To select multiple dashboards and filters hold Ctrl (Command on macOS).
    5. The Filter field contains a list of all filters owned by the user specified in From User. Select the name of the filters for which ownership will change to the user defined in To User.

      Change Dashboard or Filter Ownership can be used to change the ownership of the selected dashboards (including filters within the dashboard with the same owner), selected filters, or selected dashboards and filters.
      change dashboard owndership select
    6. Click Preview to see an overview before committing changes. Click Run to change ownership.

      change dashboard ownership preview

Bulk Fix Resolutions

Use the Bulk Fix Resolution built-in script to change the Resolution field on multiple issues at once. Problems with imports, workflow modifications and Jira migrations can all cause incorrect Resolution values for multiple issues. Bulk Fix Resolution allows you to modify the Resolution value for all issues returned by a JQL query without entering the database or re-indexing.

  1. From ScriptRunner, navigate to Built-in Scripts→Bulk Fix Resolutions.

  2. Under Filter ID select a filter. Issues returned by this filter are modified.

    bulk fix res filterid
    Only saved JQL filters show up in Filter ID. For more information on how to create and save custom filters see Saving Your Search as a Filter.
  3. In New Resolution, select the correct resolution value.

    bulk fix res newres
    For Unresolved select None.
  4. Click Run. All issues matching the Filter ID are changed to the specified new resolution.

    1. Optionally, click Preview to see an overview of the changes before running the script.

      bulk fix res run

It is recommended that incorrect resolution values are deleted after completing a bulk fix resolution. To do this:

  1. Navigate to Issues→Resolutions from the Jira Administration console.

  2. Click Delete next to the incorrect resolution value.

Copy Project

Use the Copy Projects built-in script to create a new project based on the configuration of an existing project in the Jira instance, allowing administration users to quickly and efficiently create projects without the need for timely manual configuration. This script copies the following to a new project:

  • Schemes

  • Role memberships

  • Custom field configurations

  • Agile boards related to the project (Optional)

  • Filters and dashboards that begin with the project key (Optional)

    This is useful when processes require giving new users a tailored dashboard and a set of filters related to their project.
  • Versions (Optional)

  • Components (Optional)

  • Issues and their attachments (Optional. Components and Versions must be selected)

    This is useful for creating template projects with issues and template documents.
    When copying issues, all links on issues in the source project will be copied exactly, with one exception: Intra-project links are reproduced on the respective issues in the copied project. For example, if you are copying project A to project B, and issue A1 depends on A2, then B1 will depend on B2.
    1. From ScriptRunner, navigate to Built-in Scripts→Copy Project.

    2. Under Source Project, select the project you wish to copy.

    3. In Target Project Key, enter the key for the project to be created.

    4. In Target Project Name, enter the full name of the project to be created.

    5. Optionally, tick checkboxes to define what is copied to the new project. Additional options are available if a Service Desk project is selected in Source Project:

      1. Tick Copy Versions to copy all versions.

      2. Tick Copy Components to copy all components.

      3. Tick Copy Issues to copy all issues. This requires both Copy Versions and Copy Components to be ticked.

      4. Tick Copy Project-Specific Dashboards and Filters, to copy all dashboards and filters beginning with the project key of the source project.

        Service Desk only:

      5. Tick Copy Service Desk Request Types to copy all request types from the source Service Desk project.

      6. Tick Copy Queue to copy the Source Project queue to the target project, replacing the default project queues.

      7. Tick Copy Service Desk Organizations to copy project specific organizations to the target project.

      8. Tick Copy SLA to copy the Source Project SLA to the target project, replacing the default project SLAs.

        copy project optional
    6. From Copy Rapid Board, select the name of a rapid board. A board in the new project will be set up using the configuration of the selected board. To copy no board configurations select Do not copy board.

    7. In Target Board Name, type the name of the new board which will be created using the configuration of the board selected in Copy Rapid Board. If no board was selected leave blank.

    8. Click Preview to check if validation has passed.

      copy project preview
    9. Click Run to create the new project.

Copy Custom Field Values

Save time configuring custom fields by bulk copying values from one custom field to another using Copy Custom Field Values. This script copies the values from a configured custom field to another on the same Jira instance. Copy Custom Field Values can be used to save time when changing the type of a custom field, eliminating the need for manual input of values, or when creating custom fields with the same values.

You can copy custom fields between the following field types:

  • Single-select to multi-select.

  • Multi-select to single-select (retains first value only).

  • Multi-select to text (comma separated values).

  • Short text to unlimited text.

  • Multi-select to multi-select.

    1. From ScriptRunner, navigate to Built-in Scripts→Copy Custom Field Values.

    2. Select a Filter ID. Issues returned by this filter are modified only if they contain both the source and target custom fields.

      Only saved JQL filters show up in Filter ID. For more information on how to create and save custom filters see Saving Your Search as a Filter.
      copy custom field values filterid
    3. Under Source Field, select the field from which all values are copied.

      For select lists, if an option from the source field is not configured to the available target field, it is skipped. For example, the source field has options AA and BB configured, and the target field has options AA and CC configured. In this case only option AA is copied to the target field.
    4. Under Target Field, select the field to which all values are copied from the Source Field.

      If the target field already contains values, they are overwritten by any copied values.
    5. Click Preview to see the number of issues affected by the change.

      copy custom field values preview
    6. Click Run. Source fields are copied to target fields where applicable.

Bulk Import Custom Field Values

Use this script to import a list of field values and save time manually creating them.

  1. From ScriptRunner, navigate to Built-in Scripts→Bulk Import Custom Field Values.

  2. In Name of Custom Field Config Scheme, select the custom field configuration scheme you wish to add the new field values to. These are shown below each available custom field.

    bulk import custom field config scheme
    For more information on configuring custom fields see Configuring a Custom Field.
  3. In the Values to Import text field, enter the field options. Enter each option on a new line. For example:

    bulk import custom field values

    For cascading-select lists, enter the sub-options indented (tab or two spaces) under their parents (as shown below).

    ParentA
        ChildA1
        ChildA2
    ParentB
        ChildB
    Bulk Import Custom Fields skips over existing options so fields can be updated using this script without fear of accidental deletion or duplication.
  4. Click Preview to see details of the changes.

  5. Click Run to add the new field options. Options are viewed in Issues→Custom Fields (in the Jira Administration console) by clicking the Cog icon next to custom field name and selecting Configure.

    bulk import custom field options

Split Custom Field Context

The Split Custom Field context script allows users to separate custom field contexts between two projects, duplicating the custom field values across the projects. This means they can be configured separately.

split custom field context diagram

For example, two projects are using the same custom field configuration scheme, one project needs to delete/add/deactivate a field value from a multi-select list but the other project does not. Use the Split Custom Field Values script to split the shared custom field context between two projects. This creates a new context, duplicating option values, and migrating all issue field values, allowing custom field values to be edited independently.

Although it is possible to create a new field context and move the project across, this causes values for all fields associated with the original context to be lost. Using Split Custom Field Context allows field contexts to be split between projects without the need to re-enter field information.

  1. From ScriptRunner, navigate to Built-in Scripts→Split Custom Field Context.

  2. In Name of Custom Field Config Scheme, select the name of the config scheme which will contain the new custom field context created.

    split custom field values name of custom field
  3. Under Project Name, select the project(s) to move to the new context. This field shows projects using the field configuration selected in step 2.

    split custom field context project name
  4. Issue types associated with the selected configuration scheme and selected project show in the Issue Types field. Select the issue type(s) to associate with the new scheme. Leave blank to select all options.

  5. Under New Context Name, give a name to the new context.

  6. Click Preview to see a summary of changes including the number of issues to be updated.

    split custom field values preview
  7. Click Run to create a new custom field context for each project selected.

Script Registry

Use Script Registry to view all groovy scripts in:

  • Workflows

  • Listeners

  • Fields

  • REST Endpoints

  • Behaviours

  • Fragments

  • Escalation Services

Script Registry lists scripts, their type, content, and location. Each script undergoes Static Type Checking to ensure it is written correctly.

It is recommended that Script Registry is used on staging instances after upgrading Jira to validate that all necessary APIs are available.
  1. From ScriptRunner, navigate to Built-in Scripts→Script Registry.

  2. Click Run to view all configured scripts. All groovy scripts on the Jira instance are listed.

    script registry run
  3. Scripts are sorted into category tabs, showing the number of scripts in each category. Click on a tab to display all scripts of that kind.

    script registry tabs

Clear Jira or Groovy Caches

Use the Clear Groovy or Jira Internal Caches built-in script to manually clear caches if automated clearing fails (Groovy), or changes have been made in the database (Jira). Triggering a clear cache event, this built-in script clears all caches responding to the event.

  • Clear Groovy Caches - Manually clear groovy ClassLoader caches if they fail to clear automatically. Classes should be reloaded when modified, however dependant classes can fail to reload. Use the Clear Groovy or Jira Internal Caches built-in script to trigger a reload of all Groovy classes.

  • Clear Jira Internal Caches - Clear Jira internal caches after changing something in the backend such as modifying a database table. Clearing the internal cache is safe for use on production environments, as the cache is cleared during enabling/disabling of plugins.

    1. From ScriptRunner, navigate to Built-in Scripts→Clear Jira or Groovy Caches.

    2. Select the cache to be cleared under Which Cache?.

      clear caches select cache
    3. Click Run to clear the selected cache.

      Expect a delay in screen refresh as classes are recompiled when clearing caches.

Bulk Copy SLAs

Use the Bulk Copy SLA Configuration built-in script to copy the Service Level Agreement (SLA) configuration from one Service Desk project to one or more additional Service Desk projects. Remove the need to input SLAs into each project manually by copying one configuration to multiple Service Desk projects on the same Jira Service Desk instance.

The SLA configuration of the target project(s) are overwritten by the configuration copied from the source project.

  1. From ScriptRunner, navigate to Built-in Scripts→Bulk Copy SLA Configuration.

  2. Select a Source Project from the drop down list. This list shows all Service Desk projects on the active Jira instance.

  3. Enter the Destination Project names to which the SLA configuration of the source project will be copied to. Multiple projects can be added here.

    bulk copy sla config
  4. Click Preview to see an overview of changes.

    If there is a mismatch of issue types between the source project and target project(s), irrelevant information is copied to the destination SLA configuration. Click Preview to display a list warnings when mismatches occur. Mismatches do not cause errors, however it is recommended that you ensure the source and target projects contain the same issue types.
    bulk copy sla config preview
  5. Click Run to accept all changes and run the script.

List Scheduled Jobs

Use the List Scheduled Jobs built-in script to view details of all scheduled jobs on the current instance. Results show the Job ID, Interval, Next Run, and Run Mode of each job, listed below the feature under which they run.

  1. From ScriptRunner, navigate to Built-in Scripts→List Scheduled Jobs.

  2. Click Run to view a list of all scheduled jobs.

list scheduled jobs

View Server Log Files

Use View Server Log Files to see the last N number of lines of the desired server log. This built-in script allows users to check log files without the need to connect to and have access to the server.

For information on more advanced logging in ScriptRunner see Advanced Logging
  1. From ScriptRunner, navigate to Built-in Scripts→View Server Log Files.

  2. Select the name of the log file to view from Log File.

  3. Enter a value into Number of Lines. The script returns the last N number of lines from the log file.

  4. Click Run to view the results.

view server log files
Increase the logging level to help debug scripts. See this knowledge base article for more information.

Re-index Issues

Use Re-index Issues to re-index a project or issues returned by a filter. Re-indexing may be required after a change to the database, or when an indexing issue occurs. The Re-index Issues built-in script allows you to re-index only those issues affected by the changes, saving time and reducing downtime on your Jira instance.

  1. From ScriptRunner, navigate to Built-in Scripts→Re-index Issues.

  2. To specify which issues need re-indexing, enter either a Filter ID or Project Key:

    1. Pick a filter from the list to select issues by Filter ID. Only issues returned by this filter are re-indexed.

      Only saved JQL filters show up in Filter ID. For more information on how to create and save custom filters see Saving Your Search as a Filter.
    2. Enter a Project Key to select issues based on project. All issues in the corresponding project are re-indexed.

  3. Click Preview to see an overview of how many issues will be re-indexed.

    reindex issues preview
  4. Click Run to re-index.

Configuration Exporter

Use the Configuration Exporter built-in script to export extension configuration information to a descriptor (YAML) file. The YAML file contains information required to configure built-in extension points such as listeners, hooks, macros, and UI fragments, etc.

Using this file within Script Plugins when migrating from one instance to another, allows scripts to be automatically configured, saving time and ensuring consistency across instances.

  1. To create the YAML file, first navigate to Built-in Scripts→Configuration Exporter from ScriptRunner.

  2. Items with configurations available for export are shown in the Export What field. Select the items you wish to generate the YAML for. Multiple items can be exported to one YAML file.

    config-exporter

    The Note field is used to distinguish one item from another, only items with notes are shown. Ensure the Note field has a value for all configuration items you wish to export.
  3. Click Run to generate the YAML snippet. Copy the snippet and paste it into your script-runner.yaml file.

    You can manually edit the code yourself to add more items, though it’s generally easier to re-generate the YAML file using the Configuration Exporter script.
For more information on using YAML files to create script plugins see Creating a Script Plugin.

Workflow functions

Conditions

JQL Query Condition

Enter a JQL query, if the current issue would be returned by the query, the condition is allowed. This is less flexible but possibly simpler to use than the simple scripted condition if you don’t have a programming background.

Before adding the query to your workflow you can test it by going to Admin → Built-In Scripts → Allows the transition if this query matches a JQL query. Enter the query and a sample issue key.

This is reasonably efficient even if the query would return millions of issues, as the query will be modified to something like:

{issue = 15130} AND {issuetype = "Task"}

Disclaimer: this wasn’t my idea originally.

Simple scripted condition

This script lets you use all the flexibility of a scripting language without having to get your hands too dirty.

Enter some code in the condition box. If this evaluates to true the condition passes and the transition is shown, if not it isn’t. You do not need to explicity return true or false…​ the value of the last statement sets the return value (when cast to a Boolean). However feel free to return true or false if you like.

There are a number of samples shown, if you click the link the code will be copied into the text box. You can then edit it as desired, or write something completely new.

There are plenty of other plugins that provide similar functionality, for instance checking values of fields. However this gives you greater flexibility, e.g. you can check something xor something_else, plus more advanced conditions such as all issues this one depends on are resolved.

Technical Stuff for Advanced Users

The conditions are mini groovy scripts. When they are modified, they are automatically compiled and cached.

The following variables are available:

Name

Type

Sample Usage

cfValues

java.util.Map

Table of custom field names (not IDs) to their values for this issue, used for quickly looking up CF values

currentUser

com.atlassian.crowd.embedded.api.User

User doing the transition, name is available as currentUser.name

attachmentManager

com.atlassian.jira.issue.AttachmentManager

Used for getting attachments

issue

com.atlassian.jira.issue.MutableIssue

Current issue in this transition

issueLinkManager

com.atlassian.jira.issue.link.IssueLinkManager

Used for checking links

customFieldManager

com.atlassian.jira.issue.CustomFieldManager

Could be used if the cfValues is not sufficient, or you need to check values on other issues

originalIssue

com.atlassian.jira.issue.Issue

In validators, a copy of this issue before the user changed it in this transition. So for instance, to ensure the assignee was changed during a transition, in your validation condition you might put: issue.assignee != originalIssue.assignee

baseUrl

java.lang.String

Base URL of JIRA, used for building URL links

Many of the built-in functions have a condtion option, as it’s not possible in JIRA to only activate a workflow post-function if some other condition is true. You can test conditions before setting them up for real on the Condition Tester admin script. If there is a syntax error it will be shown.

Safe navigation

Let’s say you want to check the resolution is Won’t Fix. You could write

issue.resolution.name == "Won't Fix"

However, if the resolution is not set at all, ie is null, this code would produce an error:

im2

Therefore it should be written with the safe-navigation operator, ie:

issue.resolution?.name == "Won't Fix"
Note the question mark.

Power Assertions

Power assertions are a very useful debugging tool. On the questionable line you are having problems with, simply begin it with assert.

As an example, let’s say I am trying to check the value for a custom field called MySelect, which has two values Yes and No. I want my workflow function to happen only if the value is set to Yes. As a naive first step, I try cfValues['MySelect'] == "Yes", this doesn’t work for an issue where it should, so I add the assert:

assert cfValues['MySelect'] == "Yes"

which gives the following failure:

image2013 3 25%2022%3A2%3A6

Note this is a bit counter-intuitive, as apparently both sides are "Yes", yet the == was false. Whenever you see something odd like this check the type of class:

assert cfValues['MySelect'].class == String.class
               |            |     |
               Yes          |     false
                           class com.atlassian.jira.issue.customfields.option.LazyLoadedOption

This tells us that value for this custom field key is actually a LazyLoadedOption, and we should call the getValue() method. It just so happens that the toString() method of Option returns the value, but you can’t compare them as strings.

Similarly, a multiselect is stored as a List of Options, so the correct code to use to check to see if a particular value is present is:

cfValues['MyMultiSelect']*.value.contains("AAA")

That is, use the spread-dot operator to call the getValue() method on every item, then we check if "AAA" is in that list.

In short, if you are having problems use the Condition Tester built-in script and add assert statements.

Remove the assert before using as a real condition - the assertion doesn’t return anything as such so your condition will always evaluate to false if you leave the assert in.

All sub-tasks must be resolved

Does what it says on the tin. The transition won’t be shown unless all sub-tasks are resolved. You can specify any resolution that the sub-tasks must be in or choose a particular one.

Validators

Simple scripted validator

The Simple Scripted Validator is a simple check for validating issues after they have been changed in a workflow transaction. You can validate that changes to multiple fields are coherent according to your business processes, likewise attachments, issue links, and parent/child relationships.

There are many uses for this function, several of them documented in the specific examples section.

Note that you can access the fields of the issue before it was changed in this transition using the originalIssue variable, see Technical Stuff for Advanced Users.

Post-functions

Send a custom email

This allows you to send email(s) after a transition, to support your business processes. The alternative to this is to define a new event type, new velocity templates, and map the event to the templates as documented here.

Using this post-function is comparitively simpler and more flexible.

When setting this up in the workflow you should preview it first, which will let you test the appearance of the email, and the recipient list etc.

Taking an example. Say you wanted to send a mail to the reporter and the CCB board when an issue with Critical or above priority is created. (A more useful example might be an email requesting approval for a sub-task for instance, or letting the QA team know an issue is ready for test).

Edit your workflow, and add a Script Post-function on the transition you want the email to be sent on, then choose Send Custom Email.

For Critical and Blocker priorities the condition might be

issue.priority.name in ['Critical', 'Blocker']

An email template example:

Dear ${issue.assignee?.displayName},

The ${issue.issueType.name} ${issue.key} with priority ${issue.priority?.name} has been assigned to you.

Description: $issue.description

<% if (mostRecentComment)
    out << "Last comment: " << mostRecentComment
%>

Custom field value: <% out <<
	issue.getCustomFieldValue(
    	com.atlassian.jira.component.ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("TextFieldB")
    ) %>

Regards,
${issue.reporter?.displayName}

Now fill in the email subject template. Clicking on the link below will pre-fill an example.

Set the email format. If you want to use an HTML template choose HTML.

Now you need to define the recipients

  • The field marked "To addresses" allows you to enter email addresses, for instance ccb_board@acme.com in this example.

  • "To issue fields" lets you specify fields on the issue. Both user and string fields are acceptable, for example assignee, reporter, watchers, user or user group custom fields, or custom fields holding valid email address

Recipients need to be active users in order to receive emails. You can get around this check by adding their email to the mail object’s recipient list.

In the "Preview Issue Key" enter the key of an existing issue.

Press preview.

If there were no errors you should see something like the following:

email preview

Note the highlighted text which tells you that the condition evaluated to true, so an email would be fired in this case. You should now test with an issue that doesn’t have one of those priorities and make sure the condition evaluates to false, and for extra points, one where no priority is set at all.

Add the Send Custom Email post-function to the ToCCB_Board transition.

Finally, having tested that the recipients, the subject and email body look correct, you can add this as a post-function to your workflow.

Emails are added to your mail queue, which is flushed every one minute. If mail is disabled or no mail servers have been set up this won’t work (obviously)
Generally, where used as a post-function, this should be after the "Store the issue" function, so if you need access to links and attachments, you will get them.
If you are facing issues with characters appearing as question marks (???) then, in most cases, problems are due to a misconfiguration in the encoding, see JIRA Application internationalisation and encoding troubleshooting.
Comments in Emails

You will commonly want to reference the last comment in your email templates. The following variables are available for use in the templates:

Variable name

Description

lastComment

The comment made in this transition, if there was one. If not this will be null, hence you should wrap it an if block as shown in the example template.

In previous versions of the plugin this was set to the last comment on the issue, regardless if it was made in this transition or not. This was not the intended behaviour, but if you were relying on it then update your templates to use`mostRecentComment` instead.

mostRecentComment

The comment made in this transition, or if there wasn’t one, the most recent comment made prior to this transition.

latestPriorComment

The latest comment made prior to this transition. Using this, plus lastComment, can display a conversation thread.

Additional Configuration in Emails

You may notice the syntax for getting custom fields is a bit clunky, as the template engine does not allow you to use the import keyword. Rather than doing this, you can pass in a config map to the bindings for both the subject and body templates.

This is done in the Condition and Configuration section. Note that as this is used for the condition you must continue to return a boolean value to tell the system whether to send the email or not.

You can also pass in closures (functions) that you can call from the body template. A simple example of a Condition and Configuration section that defines config variables:

import com.atlassian.gzipfilter.org.apache.commons.lang.StringUtils
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager

def customFieldManager = ComponentAccessor.getComponent(CustomFieldManager)
def storyPointsCf = customFieldManager.getCustomFieldObjectByName("TextFieldB")

// add value of story points to config map
config.storyPoints = issue.getCustomFieldValue(storyPointsCf)

// add a closure
config.capitalize = { String str ->
    StringUtils.capitalize(str)
}

return true

The template:

Dear User,

Story Points: ${storyPoints}

Function: ${capitalize.call('hello sailor')}

Note the syntax for calling the closure from the template.

If the condition is going to return false, the config variables are irrelevant. So you can return false early…​

def condition = issue.status.name == "Open" && ...
if (! condition) return false

def expensiveConfigVars = [foo: "bar"]

config.putAll (expensiveConfigVars)
Dynamic Control over Recipients And From

Sometimes you want to dynamically alter the recipient or the sender, depending on issue attributes. The Condition and Configuration section has access to an empty mail object (a com.atlassian.mail.Email.

You can set attributes on it here, for example:

if (issue.priority.name == "Highest") {
    mail.setTo("head-honcho@acme.com")
} else {
    mail.setTo("chair-moistener@acme.com")
}

If you do it like this the To value of the configuration field becomes redundant, and you can leave it empty.

Another possible use of that field is to set email addresses stored in a custom field (that may not appear in the 'To issue fields' dropdown), for example:

import com.atlassian.jira.component.ComponentAccessor

def multiSelectAddressesCF = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Multi Select Email Adresses")
def recipients = issue.getCustomFieldValue(multiSelectAddressesCF)?.join(",")

mail.setTo(recipients)

To support backwards compatibility the validation remains in place, so you will need to check the box at the bottom of the form which disables the validation of the configuration.:

email disable validation

If you want you can construct the mail completely dynamically, including the subject and the body, and ignore the rest of the form.

Rendering Wiki Markup Fields

If you are sending HTML mails you will want fields containing wiki markup to be converted to HTML. You can use the following code in your templates.

${helper.render(issue.description)}
Only the standard Atlassian Wiki Renderer for JIRA is supported, not custom plugins supplying their own renderers.
Attachments in Emails

Unlike the standard JIRA email facility, this function can optionally include issue attachments in the mail. Useful for sending to offsite collaborators, who may not have JIRA accounts.

You have the choice of sending none, all, new (added this transition), or you can define a custom callback for complete control over what attachments to include. There are inline examples for this.

You may need to set specific JavaMail properties in JIRA’s startup settings so that different mail clients will get attachments with filenames properly encoded.

For example, if an attachment’s filename is particularly long and contains characters outside the ASCII range, then certain versions of Microsoft Outlook won’t render the filename correctly, and users may even have trouble downloading the attachment.

To address that, set the following properties in your JIRA instance’s startup scripts:

mail.mime.encodefilename=true
mail.mime.decodefilename=true

Fair warning: your mileage may vary. Whether this is needed for your instance will be dependent on your specific environment. Implementations of RFC 2231 vary widely across different mail clients.

If you’re trying to use the 'new' attachments setting in the listener version of this script it’s important to be aware of what 'new' will pick up.

By default, 'new' on the Issue Commented event will get any attachments found in the comment body. On Issue Created all attachments will be added. On other events, such as Issue Updated, any attachment that was added as part of that event’s changeLog will be included.

Getting old and new custom field values

For events use:

`!`<%
def change = event?.getChangeLog()?.getRelated("ChildChangeItem").find {it.field == "Name of custom field"}
if (change) {
    out << 'Old: ' + change.oldstring + "\n"
    out << 'New: ' + change.newstring
}
%>`!`

For workflow functions use:

`!`<%
def change = transientVars.changeItems.find {it.field == "Name of custom field"}
if (change) {
    out << 'Old: ' + change.oldstring + "\n"
    out << 'New: ' + change.newstring
}
%>`!`
Control over From Address

By default we use the default from address for the SMTP server you have configured. This is because, in corporate environments, the mail relay will normally be configured to only allow a single sender. Encouraging people to set the From address caused confusion when the mail relay blocked it. Instead, you can set the Reply-to address, in the form, or using mail.setReplyTo(…​).

You can also set the way the sender appears in the mail client using mail.setFromName("Your Boss").

If you need to set the From address you can:

mail.setFrom("your-boss@example.com")
Including Query Results

You can include the results of a query in your email template - this will utilise the same velocity templates that JIRA uses.

In the template use: /opt/avst_prod_bm_agnt1/home/xml-data/build-dir/RELEASE-SRD-RELEASE/docs/..

${helper.issueTable("project = FOO and assignee = currentUser()", 10)}
The number 10 is an optional parameter that indicates the maximum number of issues to show. If there are more than this number, we do not currently provide links to the issue navigator to retrieve them.
There is an outstanding JIRA bug JRA-40986 which prevents images being inlined into the email, so recipients will need network connectivity to the JIRA instance in order for images to show.
Adding Generated Attachments

Sometimes you may wish to generate your own attachment and include it in the mail. Examples include:

  • an automatically generated invoice

  • a custom PDF or Office document

  • an Excel file with the output from a query

As you have access to the mail object in the Condition and Configuration section, you can add the attachment here, for example:

import javax.mail.internet.MimeBodyPart
import javax.mail.internet.MimeMultipart
import javax.mail.internet.MimeUtility

import javax.activation.DataHandler
import javax.activation.FileDataSource

def mp = new MimeMultipart("mixed")

def bodyPart = new MimeBodyPart()
bodyPart.setContent("some text content", "text/plain")

//def attFds = new FileDataSource("/path/to/file.xls")
//bodyPart.setDataHandler(new DataHandler(attFds))

bodyPart.setFileName(MimeUtility.encodeText("foo.txt"))
mp.addBodyPart(bodyPart)
mail.setMultipart(mp)

true
A full explanation of everything you can do with JavaMail is out of scope, however above we demonstrate how to add a simple text attachment, and, commented out, how to attach a file from the file system.

Sending mail from a non-issue event

The built-in script supports only issue-related events, and also "watcher added/removed" events.

If you want to send mail from an event not related to an issue, such as when a new Version or user is created, you can use a Custom Listener configure to handle the appropriate event, and a script such as this:

import com.atlassian.crowd.event.user.UserCreatedEvent
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.mail.Email
import com.atlassian.mail.queue.SingleMailQueueItem

def event = event as UserCreatedEvent

def email = new Email("someuser@example.com")
email.setSubject("User created")
email.setBody("User: ${event.user.displayName} created...")
// email.setMimeType("text/html") // for messages with an html body

SingleMailQueueItem item = new SingleMailQueueItem(email)
ComponentAccessor.getMailQueue().addItem(item)

Post to chat service

This post-function/listener allows you to send a personalised custom message to either your HipChat or your Slack room.

There are bindings for the issue and the event object so that you can fetch the information pertinent to your event.

For example - you can choose to listen to Issue Resolved Event and have a message that outputs the resolution and the user that resolved the issue

"Issue $issue resolved with resolution <% out << issue.resolution?.name %>. by <% out << event.getUser() %>"
Configuration
HipChat

In order to configure this feature, you need an API access token and the URL for your HipChat domain.

From your browser, navigate to your HipChat URL. Go to your account settings, you should see a link named "API access".

You need to create a new token, or use one that is currently in use, but it should have at least the "Send Message" and "View Room" permissions.

You can see how this screen looks here:

hipchat access token

With the token and your link you should be able to fill the details in the form as follows:

post message hipchat token

As soon as you enter the bearer token, the list of the rooms should update, and you will be able to pick one.

Slack

In this case, the configuration is slightly more complicated. You need a Slack app with an access token.

Creating your Slack App

First, you will need to create an app with a name and a Slack domain. To do so, navigate to the "Your Apps" page on Slack’s website and click "Create an App."

As an example, you could write "ScriptRunner Integration" as the name and then choose your domain as the development Slack workspace. You’ll need to either sign into your workspace or create a new workspace in order to add it as your development workspace.

Navigate to the tab "Bot Users" and add a new bot. You will need a display name and a default name for your bot.

Navigate into your newly created app to the "OAuth and Permissions" tab as you can see here

slack-example-app

Before installing your app, you are going to need to add permissions scopes to your app. This tells your app the permissions that it has access to, such as sending messages or viewing rooms.

The app will need the bot permission. This will allow the app to read the list of rooms within your space, and send messages as a bot.

After including these permissions you will be able to install your app using the "Install App to Workspace" button within the same tab.

Install your app. Afterwards you will see a Bot Oauth Access token at the top of the page, this is the token that the feature needs to send messages, hit the copy button as you can see in the following picture:

slack-bot-token

At the app form in your ScriptRunner, select "Slack" and paste your token. It should automatically populate the room field with a list of rooms.

String templating in groovy

There are two variables available for you to use in your template, "issue" and "event". This last one is only available if you configure this feature as a listener.

Check the documentation about constructing your string template for the last field of the form:

As a summary, in order to call a property, you can call it with the name

"Your issue $issue has been updated."

But you can do more complex calls to your object by using the <% %> notation:

"Your Issue $issue was updated by <% out << event.getUser() %> on <% out << event.getTime() %>."

Fires an event when condition is true

This built-in script provides an alternative method for sending emails under certain conditions. Provide the name of the event to be fired, and the condition under which to fire it (see above). This method lets you use the "power" of velocity templates, but you will need to create a new event type, and associate it with your templates.

This function is useful if you only want to send mails to your Project Manager for high-priority issues or something similar.

Fast-track transition an issue

This post-function will automatically transition an issue if the provided condition holds true. Let’s take a contrived example again. Imagine that all new Critical priority issues should go to the CCB for approval when the issue is created.

im3

To realise this we add this post-function on the Create Issue transition.

fast track to ccb
It’s not obvious how to get to the properties of the Create Issue transition. Click the first step, then the action will be available in the box at top right.
Place this as the last post-function, or at least after the Fire Event function. If you don’t do this, it won’t work.

So, if the issue has priority == Critical the issue will automatically have the "Requires CCB Approval" action applied on it.

When choosing a workflow action, note that you may see multiple actions of the same name. The key thing is the ID. It must be a valid transition from the target state of this action. You can get the ID by mousing over the links in the transition.

Note that if you need to provide additional parameters for doing a transition, for instance when Resolving an issue, you can enter code in the Additional Code field. The following sets the resolution and assigns to a user:

import com.atlassian.jira.component.ComponentAccessor

def constantsManager = ComponentAccessor.getConstantsManager()
def resolution = constantsManager.resolutions.findByName("Done")
issueInputParameters.setResolutionId(resolution.id)

issueInputParameters.setAssigneeId("admin")

We use IssueService to conduct the transition, which makes an effort to behave in the same way as the user interface.

If the field you are updating does not appear on the screen for the transition, or there is no screen, you should add the following line:

issueInputParameters.skipScreenCheck()

Using IssueService directly, you can only update fields on the issue if there is a screen for that transition, and you can only update those fields that appear on the screen. However, we allow you to update the issue even if there is no screen.

You can always add a comment using issueInputParameters.setComment('Some comment'). Do not also ignore the screen check if you do this, there is a bug that will cause the comment to be added twice.
Transition Options

These options allow you to skip validators, conditions and permissions on transition. For example this is useful when you want to force the transition to happen as a user that does not have the correct permissions, or you want to make use of the standard condition that hides the transition from users.

You can use this script multiple times on a transition, eg the Create Issue transition, to effect different default start statuses for an issue, depending on some value or other.

Clones the current issue to a new one. User can select between copying, All the fields, None of them or certain fields, from source to the new issue.

There is also the possibility to select the user whom on behalf the issue will be cloned (As User field), it affects the creator and the reporter of the new cloned issue. If empty the currently logged in user will be used.

You can specify an issue type and/or target project, but if you leave them blank the clonee will have the same project and type as the cloner.

Specify also the link type and direction…​ this will normally be Clones, but doesn’t have to be.

If you want to override some of the field values rather than have them inherited from the source issue you can specify that in the Additional Code field. For example the following snippet will set the summary field, and a custom field called MyCustomFieldType to "my value":

issue.summary = 'Cloned issue'
def cf = customFieldManager.getCustomFieldObjects(issue).find {it.name == 'MyCustomFieldType'}
issue.setCustomFieldValue(cf, "my value")

As of 2.1, outward and inward links are also copied. So if issue A depends on issue B, after cloning to issue C then issue C will also depend on issue B. And similarly if issue B was depended on by issue A.

You can override this behaviour to copy links selectively or disable altogether by providing a callback (a closure) called checkLink in the Additional Code field. The callback will be called for every link found and should return true or false. This is passed one parameter, the IssueLink object. For example, to disable the cloning of links altogether use this code:

checkLink = {link -> false};

To enable cloning of all links except links of the type Clones use:

checkLink = {link -> link.issueLinkType.name != "Clones"}
Control of cloning attachments

In a similar way, you can control which attachments are copied. To disable attachment copying completely:

checkAttachment = {attachment -> false}

To clone only the attachments added by the user jbloggs:

checkAttachment = {attachment -> attachment.authorKey == 'jbloggs'}

Due to indexing changes in JIRA 5.1, this post-function should be placed immediate after the function: Re-index an issue to keep indexes in sync with the database. If you don’t do this, the parent issue will not be indexed correctly.

After Create actions

There are some actions that can take place only after the creation of the issue (add watcher, create issue links etc), in this case you can apply additional actions after the creation of the new issue, using the doAfterCreate callback (closure).

For example, add watchers to the new issue:

import com.atlassian.jira.component.ComponentAccessor

def watcherManager = ComponentAccessor.getWatcherManager()
def userManager = ComponentAccessor.getUserManager()

doAfterCreate = {
    def userKeyToAddAsWatcher = "anuser"
    def watcher = userManager.getUserByKey(userKeyToAddAsWatcher)

    if (watcher) {
        watcherManager.startWatching(watcher, issue)
    } else {
        log.warn("User with key: ${userKeyToAddAsWatcher} does not exist therefore would not be added as a watcher")
    }
}

Another use of it could be, linking the new issue to another one. For example create a 'duplicates' issue link between the new issue and an issue with key JRA-24:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLinkTypeManager

def issueLinkTypeManager = ComponentAccessor.getComponent(IssueLinkTypeManager)
def issueManager = ComponentAccessor.getIssueManager()

doAfterCreate = {
    def issueToLinkTo = issueManager.getIssueByCurrentKey("JRA-24")
    def duplicateLinkTypeName = "Duplicate"

    def duplicateLinkType = issueLinkTypeManager.getIssueLinkTypesByName(duplicateLinkTypeName)

    if (!duplicateLinkType) {
        log.warn("Issue link type with name: ${duplicateLinkTypeName} does not exist")
    } else {
        issueLinkManager.createIssueLink(issue.id, issueToLinkTo.id, duplicateLinkType[0].id, 1, currentUser)
    }
}
The variable issue outside the doAfterCreate closure is not yet created and trying to access it will result to an error (the issue.getKey() will throw a NullPointerException). On the other hand the issue inside the doAfterCreate callback is created and exists (therefore the issue.getKey() will return the key of the new issue).

Create a sub-task

This is very similar to Clone an Issue above, except that it will create a sub-task. This function could be used multiple times to create several sub-tasks, for instance if you have an issue type that requires approval by 3 groups you might create a sub-task to represent each approval, and use the "Transition parent when all subtasks are resolved" to automatically transition the parent when each sub-task has been approved.

Let’s say we use this function twice to create two sub-tasks, so that our Finance and Marketing depts can give approval:

im5

After the transition is made on the parent issue, we have the following sub-tasks:

im6

Let’s say the information on the parent issue is not complete and Marketing reject their sub-task. Providing the two optional arguments shown are entered, the next time the parent moves through this transition then the issue will be Reopened, as opposed to another instance being created.

im7

This is also useful for sub-tasks that represent whether an issue has passed testing in a given environment.

This post-function should be placed immediate after the function: Re-index an issue to keep indexes in sync with the database. If you don’t do this, the parent issue will not be indexed correctly.
To disable or selectively copy links, use the same method as described above in Clones an issue and links.

Transition parent when all subtasks are resolved

Put this post-function on the sub-task workflow on transitions where the workflow could be set, typically Resolve Issue. If all sibling sub-tasks are resolved this will transition the parent issue using the provided action, which should be valid for the current step. If it’s not valid, nothing will happen.

Using the example above, this is put on the Approve transition of the Approval issue type. When all child Approval records are approved, the parent is automatically transitioned to the Approved status. Because you will often want to resolve the parent, for convenience you can specify the resolution to be used for the parent record, if necessary.

I have not experienced this but users have reported problems if this is not after the Fire Event…​ in the list of post-functions.

Update blocking issues

Useful for alerting participants of other issues that a blocker is resolved, etc. Configure the link type (eg "blocks") and the comment to be added to blocked issues. Adding the comment will cause a mail to be sent (according to your notification scheme).

2012 05 01 2121

Put this function on your Resolve Issue transition. It should probably also be available as a listener, but it isn’t right now.

Set Issue Security

Useful for setting the security level on an issue according to some criteria about the issue or the reporter etc. Better to do it in an automated fashion than risk leaking information.

To set this up just specify the condition (multiple sample conditions are provided), and the desired issue security level. These are grouped by issue security level scheme…​ if you are applying this to multiple projects they should all use the same issue security level scheme. If the scheme or level doesn’t exist a message will be logged in your application log file, but other than that the user will not be alerted.

It didn’t set the desired security level?
  • Check your condition using in the condition tester admin script

  • Ensure your project is using the same scheme as you specified in the script options

  • Check the positioning of the function - it should be above the "store the issue" function, or for the create transition, above the "Creates the issue originally" function

image2013 3 12%2021%3A28%3A18

Add/Remove from/to active sprint

This function has two complementary parts - either adding an issue to an active sprint, or removing it from its current sprint.

For example, on the Start Progress transition you might apply this function so it’s automatically added to the current sprint. This is not the scrum way, where the entire sprint should be planned in advance, and if you finish early you should just twiddle your thumbs (not really true). However, sometimes you finish all the work in a sprint, and it’s a pain to then have to manually add everything you’ve worked on to the current sprint.

In the function configuration you provide a board name, the function will pick the first active sprint from that board. It’s possible that a future version will allow selection of the first unstarted sprint.

Changing the sprint requires the user to have the Schedule Issues permissions. There are two options here.

Select Act as other user: No if:

  • your users all have this permission, or

  • they don’t and you want this function to be ignored if they don’t

Select Act as other user: Yes if:

  • you want the action to happen as another user, who does have the Schedule Issue permission

If you choose Yes, you need to select a user who the sprint change will be done as. This will appear in the history as though the selected user had added the issue to the sprint.

You must define via code which user to do this as, which means you have plenty of options. Typically though you would choose a named user or the project lead, so those are the two examples provided:

add to sprint config

Executing Built-In Scripts Remotely

It’s possible to automate use of built-in scripts, i.e. calling them programatically. All scripts take a JSON payload. Currently this is url-encoded…​ which is likely to change in the future.

The payload contains the names and values of the parameters, plus the name of the built-in script we want to execute. The easiest way to get these is to execute the Preview function, and copy the scriptParams parameter.

Curl example

As an example, let’s use the script which bulk imports custom field options. We need to post data similar to the following, which will add the three options AAA, BBB and CCC.

{
	"FIELD_FCS":"10208",
	"FIELD_IMPORT_VALUES":"AAA\nBBB\nCCC",
	"canned-script":"com.onresolve.scriptrunner.canned.jira.admin.BulkImportCustomFieldValues"
}

Put the above in a file, in this example called add-opt.json. Now post this to the correct endpoint using curl:

> curl -u admin:admin "http://<jira>/rest/scriptrunner/latest/canned/com.onresolve.scriptrunner.canned.jira.admin.BulkImportCustomFieldValues" -H "X-Atlassian-token: no-check" -H "Content-Type: application/json; charset=UTF-8" -H "Accept: application/json" --data "@add-opt.json"

{"output":"Added 2 new option(s) to scheme: <b>Default Configuration Scheme for SelectListA</b> for custom field: <b>SelectListA</b>. 1 option(s) already existed"}
In all the examples, enter appropriate administrator credentials.

If there is an error running the script, you will get a 500 status code, and a message.

HttpBuilder example

You can call these from code, for example from Groovy. The following example executes the Test Runner script:

testConfiguration = [
    FIELD_SCAN_PACKAGES: "com.onresolve.jira,com.onresolve.base,com.acme.scriptrunner.test",
    FIELD_TEST         : [
        "com.onresolve.jira.AAASetupAllFixtures",
    ],
]

restClient.request(POST, JSON) {
    uri.path = "/$product/rest/scriptrunner/latest/canned/com.onresolve.scriptrunner.canned.common.admin.RunUnitTests"
    send URLENC, [scriptParams: new JsonBuilder(testConfiguration)]
}

Executing Script Console Scripts Remotely

You can also execute arbitrary code in the Script Console remotely. Due the url encoding this is a bit finickity. Assuming we have the following code in a file called script.groovy

script.groovy
log.debug ("hello")
log.debug ("sailor")

We can execute it using the following curl command:

> curl -u admin:admin -X POST "http://<jira>/jira/rest/scriptrunner/latest/user/exec/" -H "X-Atlassian-token: no-check" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept: application/json" --data-urlencode "scriptText@script.groovy"

To execute a file that already exists under a script root:

> curl -u admin:admin -X POST "http://<jira>/jira/rest/scriptrunner/latest/user/exec/" -H "X-Atlassian-token: no-check" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept: application/json" --data-urlencode "scriptFile=foo/bar.groovy"

Built-In Scripts Hacking Notes

You can add your own built-in scripts, or copy and hack the existing ones. Please review the Upgrading from Previous Versions section in addition to this.

Built-in scripts must implement the com.onresolve.scriptrunner.canned.CannedScript interface.

For hacking the distributed scripts copy it out to the file system, changing the file name, the class name, and at least get the value returned by the getName() method.

Any scripts in the package com.onresolve.scriptrunner.canned.jira.admin which implements the CannedScript interface will be automatically recognised, and reloaded when they change. The recommended method of development is to have the script open in a connected instance of IntelliJ IDEA, make changes, then copy it out to one of the Script Roots, then reload the page.

Have questions? Visit the Atlassian Community to connect, share, and learn with other Atlassian users and experts, including Adaptavist staff.

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

Want to learn more? Check out courses on Adaptavist Learn, an online platform to onboard and train new users for Atlassian solutions.