Built-in Listeners

Many of the post-functions are also available as listeners. This lets you have better control over your business processes. As an example, let’s say you need a cloned issue created in a different project whenever a Critical priority issue is raised.

Although you can put the Clones an issue and links post-function on an event, if the issue is created as Major priority then subsequently edited and changed to Critical, a workflow function won’t catch it. This is where listeners come in.

Listeners are accessed in the admin UI (find the Admin cog and then Add-ons), under the ScriptRunner section and Script Listeners. In addition to the settings available when installed as workflow functions, you can also specify which events and which projects are applicable.

You can filter on projects using the Condition field as well, however as projects are much more likely to be used with listeners this is made more explicit here.

If you need to filter on issue type you can do that in the condition field, eg:

issue.issueTypeObject.name == "Bug" // && any other conditions

Example Conditions are supplied which you can use as templates.

You can add multiple configurations for the same type of listener if they have different parameters.

The listeners are not documented here as they have the same functionality as their workflow function counterparts. The listeners currently available are:

Add Watcher

This listener will add the current user as a watcher to the issue. This is useful for instance if you want all commenters to be added as watchers.

It is similar to using the Participants field in the notification scheme, however using this listener allows people to remove themselves from the watchers.

For example, to add all commenters as watchers add the listener, select the project, and choose the Issue Commented event. You can further refine this by using a condition, for instance if you wanted to restrict this behaviour to just certain components or certain priorities of issue.

Version Synchroniser Listener

This listener allows create, update, delete, move, merge etc. operations on JIRA versions across multiple projects. The listener is useful if you have multiple projects that should all have the same versions list.

It should be used carefully as will modify and delete versions in all configured target projects, if that operation is done to the source version.

The following image shows an example version synchroniser:

version synchroniser
  1. The listener will listen for version-related events in the selected source project. For this listener, the event list is pre-populated and you cannot update it.

  2. You can select multiple target projects. The listener won’t allow you to select target projects that contain the source project, as that is not meaningful.

Please understand the functionality of the listener before implementing it in your production environment. The listener propagates the following:

  1. version creates to all target projects, when a version is created in the source project

  2. version updates, if the version is already available in the target project

  3. version deletes. It does not delete the target version if it is referenced in issues and the swap option is not defined for affected issues

  4. version deletes with swaps. If any of the versions to be swapped to is not contained in the destination project, be it "Fix Versions" or "Affects Versions", the version will be created in the destination project.

  5. version merges, if the same versions are available in the target project. It does not trigger a merge operation if the merge-to version is unavailable in the target project. It will trigger a Version Deleted event instead, deleting the version in the destination project without swapping.

  6. move and reorder operations. The order of versions in target projects may not be the same as the source project, if the version names in the target project differ greatly

  7. release, and archive operations

At the current time, the release operation does not move unresolved issues in target projects, because the release event does not contain details of the version to move unresolved issues to.
At the current time, if a version deletion is initiated in a project that has no issues associated with the version to be deleted, you will not be prompted for a version swap. This is Jira functionality, as of now there is no way of prompting this swap or merge. Also, if a version merge is initiated, and the original version has no issues associated with, the merge will not happen in the destination project. This happens because in that case, Jira raises a Version Deleted Event, instead of a Version Merge event. There is one workaround, to add an issue to hold all of your versions to merge or delete with swap in the source project. A listener can be added on VersionCreate that adds all the versions created in that project to that issue as "Fix Versions" and "Affect Versions". This would solve the problem.

Before doing a release, it is advisable to run a query across the project group that the listener is configured for (i.e. source and target projects) to check for unresolved versions:

project in (P1, P2, P3) and fixVersion = 1.0 and resolution is empty

You can then do a bulk update before releasing the version in the source project.

Custom Listeners

Using groovy scripts as workflow functions instead of full-blown java plugins is much faster, more maintainable and flexible, and has all the same capabilities as java plugins. Explicit compilation is much over-rated.

As well as listening for issue events, you can also handle project, version and user-related events. Examples of this might be:

  • mail a new user when they are created in JIRA

  • notify downstream systems when a version is released

  • create link git repositories or Confluence spaces when a project is created

There are a couple of ways to structure your listener code.

The easiest, and recommended way, is to just write a script. You can use an inline script, or point to a file in your script roots, as usual.

The event object will be passed to the script in the binding. For issue related events, this will be an IssueEvent. So the simplest listener might look like:

issue resolved event

For other types of listeners, the event type will be whatever you selected to listen to, for example a ProjectUpdatedEvent.

Currently, the static type checker is not smart enough to handle different event types, so if you are working with non-issue events, you will need to cast or redefine the event object to the correct type (or you can just ignore the type checker). For example in the following case, without redefining the event, the type checker would flag oldProject as an error:

project event

If you are listening to multiple events because the code is mostly common, you can distinguish between them using instanceof:

multiple events

Issue Event Bundles

SRJIRA-1929 has been fixed, which means your specified event handler will be correctly called for the relevant events.

JIRA 6.3.10 and above broadcasts "bundles" of events, containing all relevant events. ScriptRunner will unbundle the events, and call all relevant script listeners for each event.

Previous to this fix, if you had a handler listening for the Issue Assigned event, it would only have been invoked if the issue assignment was the only action made. If someone had updated or commented an issue at the same time as assigning it, the Issue Updated or Issue Commented event would have been published, and your issue assigned handler would not run.

After this fix, it will run, but the consequence is it will be called more often than before, and perhaps when you do not expect it to. For instance, if you create an issue and set the assignee at the same time, this will invoke any Issue Assigned listeners (where previously it would not).

We advise you to write your listeners in such a way that this makes sense…​ for instance, if you want to do something when the assignee changes, then either consider the initial assignee to be a change, or to check the previous value of that field.

Or, you can choose to ignore events that were raised alongside a more "significant" event. To do this, you can make use of the IssueEventBundle, which is available in the script binding as bundle.

Example:

import com.atlassian.jira.event.issue.DelegatingJiraIssueEvent
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.jira.event.issue.IssueEventBundle
import com.atlassian.jira.event.type.EventType

        def getIncludedEvents = {
            bundle.events.findResults { includedEvent ->
                includedEvent instanceof DelegatingJiraIssueEvent ? includedEvent.asIssueEvent() : null
            } as List<IssueEvent>
        }

Custom Listener Example

The following example will post a message to a cloud instant messaging provider when a version is released. The listener is configured for the VersionReleaseEvent.

import com.atlassian.jira.event.project.VersionReleaseEvent
import groovy.json.JsonBuilder
import groovyx.net.http.ContentType
import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method

VersionReleaseEvent event = event

def http = new HTTPBuilder("https://hooks.slack.com")
http.request(Method.POST, ContentType.TEXT) {
    uri.path = "/services/XXXX/YYYYY/ZZZZZ" (1)
    body = new JsonBuilder([
        channel   : "#dev",
        username  : "webhookbot",
        text      : "Woop!: Version: *${event.version.name}* " +
            "has been released in project *${event.version.project.name}*. We're off to the pub!!", (2)
        icon_emoji: ":ghost:",
        mrkdwn    : true,
    ]).toString()
}
1 get this value from the Slack API documentation
2 get the version and project names from the event

Send a custom email (non-issue events)

Use the Send a Custom Email listener functionality to send custom emails in reaction to non-issue events such as:

  • Create a project

  • Add a new user

  • Update a component

  • Third party plugin events

See Send a custom email for more information on configuring custom emails for issue events.
Declare the event variable in Condition and Configuration to enable Code Insights.

For example, we want to send an email after a version is released. In that case, the selected event will be VersionReleaseEvent and the script in the Condition and Configuration field will be:

import com.atlassian.jira.event.project.VersionReleaseEvent

def event = event as VersionReleaseEvent
def version = event.version

// Store in then config map, the version and the project name so we can use them later in our templates
config.'versionName' = version.name
config.'projectName' = version.project.name
true

Now we can use the above variables in our Templates, for example the Email template will be:

Version $versionName for project $projectName just released.

Congratulations y'all !

JIRA Software Events

This section gives you an idea of how to use JIRA Software events. The JIRA Software events will be available automatically if JIRA Software is installed and enabled. A Script can listen to various JIRA Software events (such as SprintStartedEvent, SprintClosedEvent) and take action based on your requirements. For an example send an email with sprint data when a sprint starts or send an email with list of incomplete issues when a sprint finishes.

agile events

To get the list of incomplete issues a custom listener can be configured to listen to SprintClosedEvent with the following code fragment:

import com.atlassian.greenhopper.service.rapid.view.RapidViewService
import com.atlassian.greenhopper.service.sprint.Sprint
import com.atlassian.greenhopper.web.rapid.chart.HistoricSprintDataFactory
import com.atlassian.jira.component.ComponentAccessor
import com.onresolve.scriptrunner.runner.customisers.ast.PluginModuleTransformer
import com.onresolve.scriptrunner.runner.customisers.WithPlugin

@WithPlugin("com.pyxis.greenhopper.jira")

def historicSprintDataFactory = PluginModuleTransformer.getGreenHopperBean(HistoricSprintDataFactory)
def rapidViewService = PluginModuleTransformer.getGreenHopperBean(RapidViewService)
def user = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser()

def sprint = event.sprint as Sprint

if (sprint.state == Sprint.State.CLOSED) {
    def view = rapidViewService.getRapidView(user, sprint.rapidViewId).value
    def sprintContents = historicSprintDataFactory.getSprintOriginalContents(user, view, sprint)
    def sprintData = sprintContents.value
    if (sprintData) {
        def incompleteIssues = sprintData.contents.issuesNotCompletedInCurrentSprint*.issueId
        log.warn "incompelte issues id : ${incompleteIssues}"
    }
}

Service Desk Events

JIRA Service Desk events will be available automatically if Service Desk is installed and enabled. ScriptRunner supports large number of events which users can intercept according to the requirements.

service desk events

The following example shows how to get information from SlaThresholdsExceededEvent. The event is triggered when configured SLA’s goal time exceeds. The event contains information of the issue and at what time the resolution has been violated.

import com.atlassian.servicedesk.workinprogressapi.sla.threshold.SlaThresholdsExceededEvent
import com.onresolve.scriptrunner.runner.customisers.WithPlugin

@WithPlugin("com.atlassian.servicedesk")

def event = event as SlaThresholdsExceededEvent
def issue = event.issue
def time = event.time
log.warn "Time to resolution violated for issue : ${issue.summary} at ${time}"

"Heritage" Custom Listeners

This is the old method of using custom listeners. It still works, but you are not advised to use it for new code:

  1. Write your listener as groovy - but as a groovy class, not a groovy script. You could just extend AbstractIssueEventListener, but your class merely needs to respond to workflowEvent(IssueEvent).

  2. Copy the .groovy file to the correct place under the script roots directory (depending on its package).

  3. Go to Script Listeners, and enter the name of the package and class, e.g. com.onresolve.jira.groovy.listeners.ExampleListener. This is in the distribution so you could try this actual class, it just logs the events it catches.

If you update the groovy class it will be reloaded automatically

The simplest possible listener class looks like this:

class ExampleListener extends AbstractIssueEventListener {
    Category log = Category.getInstance(ExampleListener.class)

    @Override
    void workflowEvent(IssueEvent event) {
        log.debug "Event: ${event.getEventTypeId()} 
            fired for ${event.issue} and caught by ExampleListener"
    }
}

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.