In this page

What are automation actions?

Actions are the "doer" components of automation rules. They can perform many tasks, such as editing issues, creating sub-tasks, sending notifications, starting CI builds, running scripts, running external programs, and tons more.

There are two types of actions you can use in DevOps automations:

  1. The Automation for Jira app comes with a large selection of built-in actions designed for general use. These are viable options when designing a new DevOps automation rule.
  2. The Better DevOps Automation for Jira app comes with additional DevOps actions to solve those use cases where the built-in actions are not sufficient.

It is super-important to understand that the DevOps triggers are compatible with the built-in actions and the built-in triggers are compatible with the DevOps actions. You can freely mix those in automation rules, leveraging best of the two worlds!

Using the built-in actions

You can browse the full list of the built-in actions in the action reference of the Automation for Jira documentation.

For inspiration, we collected some for frequent DevOps automation use cases:

Action When to use? Use case example
Assign issue To change the assignee of an issue. Set the assignee of an unassigned issue to the committer automatically.
Comment on issue To add a comment to an issue. When initiating a deployment with the @deploy command, capture this in an issue comment, too.
Send Slack message To notify a team or a user via Slack.
If Slack is the communication tool your team uses (Slack is a strategic partner of Atlassian!), keep them up-to-date of the code changes without introducing another tool.
Send a Slack message with customizable information about the new commits, new branches and new tags.
Send web request To automate external tools and services that offer an HTTP-based API.
Note that almost every modern DevOps tool offers a REST API. Also, note that you can use this action to communicate with Jira's own REST API!
Start a build in the CI/CD system when a changeset is accepted in the repository.
Transition issue To automatically transition an issue through a workflow.
It keeps the Jira issues and the source code synchronized without efforts.
Transition an issue from "To Do" to "In Progress" automatically when a commit is created for it.

Using the DevOps actions

This section explains how to use the additional actions offered by the Better DevOps Automation for Jira app.

Note that although the app provides only one additional action at the moment, we will be adding new ones in future app versions. We need your feedback: tell us what action you'd need for your use case!

Run Groovy script action

This action runs Groovy scripts.

Groovy is an easy to learn, yet powerful programming language that can solve all kind of problems. As scripts can implement literally any logic, this automation action with a proper script will solve any use case for which there is no specific out-of-the-box action!

Notes:

  • This action can be used with any trigger, not just with the DevOps triggers!
  • Supported Groovy language version: 2.5.11.
Working with context variables

Groovy scripts can access the following variables that are pre-populated as "global" variables.

Variable Description
issues The collection of the input issues.
(It is a single-element list in concurrent mode.)
devops The wrapper that provides access to the DevOps-specific smart values.
(available only with the DevOps triggers)
changesetHelper A ChangesetHelper instance.
See the method reference.
classHelper A ClassHelper instance.
See the method reference and especially the classloading guide.
jiraHelper A JiraHelper instance.
See the method reference.
stringHelper A StringHelper instance.
See the method reference.
auditLog Allows easy writing to the audit log.
See usage.
jiraLog Allows easy writing to the Jira log.
See usage.
ruleContext A RuleContext instance with the execution context of the automation rule, provided by the Automation for Jira app.
See usage.

You can access these variables like any regular Groovy variable in your script:

issues.each { issue ->
	def currentIssueKey = issue.key
	jiraLog.info("Issue ${currentIssueKey} received")
}
Working with issues

There is a important difference between working with the issues collection as smart value and working with it in Groovy scripts.

When working with it as smart value, the Automation for Jira app wraps the issues items with "smart beans". Smart beans provide a few convenience fields for the issues which you can use in smart value expressions.

In Groovy scripts, however, issues is a collection of standard Issue instances. Therefore, you cannot use the convenience fields.

For example, you cannot use the convenience field issue.url in Groovy. (In this specific case, jiraHelper.getIssueUrl(issue) is the perfect replacement.)

Working with smart values

You can absolutely access all smart values in the Groovy script, although not with the curly-brace syntax. Although the expression {{foo.bar}} works in action parameters, it is not a valid syntax in Groovy.

Working with DevOps smart values

DevOps smart values are accessible through the devops variable:

def currentCommitMessage = devops.commit.message

See the complete list of the DevOps smart values.

Note that the Groovy variables also include the dynamic ones that are converted from the parameters of the triggering Genius Command! For example, if you use the "Log work" command, which supports two parameters, the following Groovy variables will also be available: devops.command, devops.duration and devops.comment.

Working with built-in smart values

To access the built-in smart values, you can easily render the curly-brace syntax to an actual value using the ruleContext.renderSmartValues() method:

def now = ruleContext.renderSmartValues("{{now}}") // datetime value as string
def command = ruleContext.renderSmartValues("{{devops.command}}") // string value

See the complete list of the built-in smart values.

Calculating smart values for other actions and conditions in Groovy

If you want to pass smart values from the script to subsequent components in the automation rule, just return those in a map data structure. Conditions and actions can access those in any later point of the rule execution. This capability makes the Run Groovy script action super-useful to pre-calculate smart values!

For example, you can create new smart values simply like:

// make "lorem" available as {{devops.foo}} and "123" as {{devops.bar}}
return [foo: "lorem", bar: 123]

You can overwrite existing smart values, too. For example, append a string to the comment text originally received from the trigger like this:

return [comment: devops.comment + " (by Better DevOps Automation for Jira)"]

Notes:

  • The items in the returned map must be serializable.
  • The "devops" map cannot be modified directly. If the script tries to do that, the rule execution will fail with an UnsupportedOperationException error.
  • For security reasons, changes to the following variables are silently ignored:
    • devops.actor
    • devops.initiator
    • devops.committerByUsername
    • devops.committerByEmailAddress
Working example

Let's say that you have two Number type custom fields: Foo (with id=10123) and Bar (with id=10456). When an issue is updated, you want to send an email with the sum of the two fields, but only if that's greater than the threshold of 10.

Steps:

  1. Login to Jira as admin, go to AdministrationSystemAutomation rules.
  2. Go to AdministrationSystemAutomation rules.
  3. Click Create rule.
  4. Select the trigger Issue updated (from the Issue triggers category).
  5. Click Save.
  6. Click New action.
  7. Select the action Run Groovy script.
    1. Enter "Calculate sum" to the Description field.
    2. Enter this Groovy script:
      import com.atlassian.jira.component.ComponentAccessor
      
      def customFieldManager = ComponentAccessor.customFieldManager
      
      def issue = issues.get(0)
      
      def fooCustomField = customFieldManager.getCustomFieldObject("customfield_10123")
      def barCustomField = customFieldManager.getCustomFieldObject("customfield_10456")
      
      def fooValue = issue.getCustomFieldValue(fooCustomField)
      def barValue = issue.getCustomFieldValue(barCustomField)
      
      // make the sum available as {{devops.fooBarValue}}
      return [fooBarValue: fooValue + barValue]
      
  8. Click Save.
  9. Click Add component and select New condition.
  10. Select the condition Advanced compare condition.
    1. Enter {{devops.fooBarValue}} to the First value field.
    2. Choose the greater than option from the Condition dropdown.
    3. Enter "10" to the Second value field.
  11. Click Save.
  12. Click New action.
  13. Select the action Send email.
    1. Choose a recipient group or enter the addresses manually.
    2. Enter the following subject:
      Threshold breached!
    3. Enter the following content (a very simple template you can easily customize to your needs):
      Sum of Foo and Bar custom fields: {{devops.fooBarValue}}.
  14. Click Save.
  15. Name your automation rule intuitively, and click Turn it on.

Note that the purpose of this example is just to demonstrate the concept. You can easily build complex calculations in Groovy for your own use case.

Working with the rule context

You can use the ruleContext variable to work with the context of the automation rule being executed. It helps you to:

Useful properties:

def triggerType = ruleContext.ruleConfigBean.trigger.type // trigger type
def triggerValue = ruleContext.ruleConfigBean.trigger.value // trigger configuration

For the complete list of the properties, check the source code of RuleContext and RuleConfigBean.

Logging

Logging is a key tool for debugging your automation rule while it is being executed. There are two facilities available for logging with a slightly different purpose:

  • Audit log: this facility is provided by the Automation for Jira app. It can be browsed through the web user interface by the rule owner. This should be your primary choice.
  • Jira log: this facility is provided by Jira. It is written to the main Jira logfile. (Therefore, it requires access to the server filesystem which makes it less practical.)

See the next sections for usage.

Writing to the audit log

The auditLog variable offers an easy way to write log entries to the audit log.

Method reference:

import com.codebarrel.automation.api.thirdparty.audit.AuditItemAssociatedType

// write messages with different severities
auditLog.info(Object message)
auditLog.error(Object message)

// associate entities with the log entry
auditLog.addAssociatedIssue(Issue issue)
auditLog.addAssociatedProject(Project project)
auditLog.addAssociatedUser(ApplicationUser user)

// associate general entities with the log entry
auditLog.addAssociatedItem(AuditItemAssociatedType type, String itemId, String itemLabel)

Values of the AuditItemAssociatedType type are listed here.

Example:

auditLog.info("Hello ${devops.actor.name}!")

Note: if you write the same line multiple times to the log, only a single entry will appear. (It's a bit unexpected, but this is how Automation for Jira works.)

Writing to the Jira log

The jiraLog variable offers an easy way to write log entries to the Jira log.

It is a regular Log4j Logger instance. Example:

jiraLog.error("Hello ${devops.actor.name}!")

Important: do not forget to configure the logging level for the "com.midori.jira.plugin.devopsautomation.action" Java package, otherwise your DEBUG-level log entries may not be visible!

Advanced topics
Scripts in bulk vs. concurrent execution

The same Groovy script can support both execution modes if you pay attention to one detail: always iterate over the devops.issues collection for the input issues.

Doing so, your Groovy script will work reliably in any execution mode and with any trigger.

Classloading

By default, all classes that are available for the Better DevOps Automation app's internals are available for the Groovy scripts, too. It means that the majority of the Jira classes and services can be used without any configuration.

But, if your script wants to use classes from another Jira app (typically when your script implements an integration with that app), you have to add the @PluginClassLoader annotation to the script. Parametrizing it with the other app's app key enables the Groovy runtime to load classes also from that app:

@PluginClassLoader("com.atlassian.streams.streams-thirdparty-plugin") // Jira Activity Stream app
@PluginClassLoader("com.almworks.jira.structure") // Structure app
@PluginClassLoader("is.origo.jira.tempo-plugin") // Tempo Timesheets app

// ...now you can freely import classes also from these apps:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.streams.api.UserProfile
import com.atlassian.streams.thirdparty.ActivityServiceDelegator
import com.almworks.jira.structure.api.StructureComponents
import com.tempoplugin.worklog.v4.services.WorklogService

Note that under the hood it is not a regular annotation, it does not need be imported, it can only be used in the beginning of the script, and it does not support multiple app keys.

Sometimes you need to pull in a module implementation from another app by its interface. For that, just import the interface, then get the module implementation with classHelper.getPluginModule():

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

import com.tempoplugin.worklog.v4.rest.InputWorklogsFactory
import com.tempoplugin.worklog.v4.services.WorklogService

def worklogService = classHelper.getPluginModule(WorklogService)
def inputWorklogsFactory = classHelper.getPluginModule(InputWorklogsFactory)

It's really that simple!

For the reference, here are the method signatures offered by classHelper:

/**
 * Returns all plugin modules that implement or extend a specific class.
 */
List<T> getPluginModules(Class<T> clazz)

/**
 * Returns a single plugin module that implements or extends a specific class.
 */
T getPluginModule(Class<T> clazz)
Limiting running time

Be careful with creating scripts that may run for a very long time or may end up in an infinite loop. Although the Automation for Jira app enforces general service limits to automation rules, the Run Groovy script action itself does not introduce additional limits.

To have a limit on the running duration, annotate your script by adding the @TimedInterrupt annotation. For example, this script with an infinite loop will time out after 5 seconds:

import groovy.transform.TimedInterrupt
import java.util.concurrent.TimeUnit

@TimedInterrupt(value = 5L, unit = TimeUnit.SECONDS)
def i = true
auditLog.info("Started...")
while (i) {
  // generates CPU load and never ends
}
auditLog.info("Completed")

If you try to run it, it will never complete. Instead, it will be terminated and there will be a new entry added to the audit log with the "FAILURE" status and the "TimeoutException: Execution timed out after 5 seconds." message.

Re-using ScriptRunner scripts

The Adaptavist Library is a large collection of Groovy scripts that were developed for their popular ScriptRunner for Jira app. Most of these can be simply copied to the Run Groovy Script action and they will work without any changes. In other cases, you need to make smaller adjustments on the script to make it work with the automation action.

To help with this, we collected the most important differences below. We also give guides to convert a ScriptRunner expression to its equivalent expression for the automation action.

For obvious reasons, it is not a comprehensive list. If you need further help, please ask us any time.

Expression for ScriptRunner Equivalent expression for the Run Groovy script automation action
@WithPlugin(...) @PluginClassLoader(...)

To make classloading work from third-party apps:
  1. Remove the @WithPlugin annotation.
  2. Remove the import line that imports WithPlugin.
  3. Add the @PluginClassLoader annotation to the first line of the script (above the imports) and pass the app key.
@PluginModule FooService fooService FooService fooService = classHelper.getPluginModule(FooService)

To access an instance of a component or service class:
  1. Remove the @PluginModule annotation.
  2. Remove the import line that imports PluginModule.
  3. Get the instance using classHelper.getPluginModule(...) (pass the target class).
  4. Don't forget to import the target class properly as described in the classloading section.
issue issues.get(0) (or iteration!)

Many ScriptRunner scripts receive a single issue object as input. In contrary, the automation action receives a collection called issues, even if there may be only one item in it.
Therefore, the strict equivalent would be getting the first issue from the collection with issues.get(0). But, you can also consider refactoring a script to iterate over issues and apply the logic to the current item. It would be a better conversion as it would work with any number of input issues: zero, one or more.
fooCollection.findByName(...) fooCollection.find { it.name == ... }

findByName() is a convenience method that ScriptRunner provides for collections. In the automation action, use a find closure for the same purpose.
log You actually have 3 options for logging:
  1. auditLog allows writing to the automation rule's audit log. (Use this if you are unsure.)
  2. jiraLog allows writing to the Jira log. (It's the most precise equivalent.)
  3. Custom logging by creating a logger with def log = Logger.getLogger(getClass()).

Generally, if you encounter class loading problems, add this to the top of the script:

@PluginClassLoader("com.onresolve.jira.groovy.groovyrunner")

Questions?

Ask us any time.