In this page

Adding a new changeset to the Jira activity stream

The activity stream in Jira is a list of events, displayed to a user in an attractive and interactive interface. They are usually recent activities by the current user and other people using the same Jira instance.

This automation adds the changeset to the activity stream as a custom activity, showing source code changes together with other events.

The activity stream is available as a configurable dashboard gadget:

It is also available filtered to a project (showing the activities only in that project):

Finally, it is available filtered to an issue (showing the activities only on that issue):


  1. Login to Jira as admin, go to AdministrationSystemAutomation rules.
  2. Click Create rule.
  3. Select the trigger Changeset accepted (from the DevOps category).
  4. Click Save.
  5. Click New action.
  6. Select the action Run Groovy script.
    1. Enter "Post activity" to the Description field.
    2. Enter this Groovy script:
      import static com.atlassian.streams.api.Html.html
      import static com.atlassian.streams.api.common.Option.some
      import org.apache.commons.lang3.StringEscapeUtils
      import org.apache.commons.lang3.StringUtils
      import org.joda.time.DateTime
      import com.atlassian.jira.component.ComponentAccessor
      import com.atlassian.streams.api.UserProfile
      import com.atlassian.streams.api.common.Either
      import com.atlassian.streams.thirdparty.api.Activity
      import com.atlassian.streams.thirdparty.api.ActivityObject
      import com.atlassian.streams.thirdparty.api.ActivityService
      import com.atlassian.streams.thirdparty.api.Application
      import com.atlassian.streams.thirdparty.api.ValidationErrors
      def MAX_SUMMARY_LENGTH = 50
      activityService = ComponentAccessor.getOSGiComponentInstanceOfType(ActivityService.class)
      applicationProperties = ComponentAccessor.applicationProperties
      if (!devops.changeset.commits) {
      application = Application.application('Better DevOps Automation', URI.create(''))
      dateTime = new DateTime()
      def commitsWithoutIssues = changesetHelper.getCommitsWithoutIssues(devops.changeset.commits, issues)
      if (commitsWithoutIssues) {
      	postActivity(devops.initiator, "<b>${devops.initiator.username}</b> committed", buildContent(commitsWithoutIssues), null)
      issues.forEach { issue ->
      	def author = changesetHelper.getCommitsByIssue(devops.changeset.commits, issue.key).stream()
      			.reduce { first, second -> second }
      			.map { commit -> commit.committerByUsername }
      			//.map { commit -> commit.committerByEmailAddress } // (alternative, see the Smart Value Reference!)
      	def commitsForIssue = changesetHelper.getCommitsByIssue(devops.changeset.commits, issue.key)
      	postActivity(author, "<b>${author.username}</b> committed to ${issue.key} - ${StringUtils.abbreviate(StringEscapeUtils.escapeHtml4(issue.summary), MAX_SUMMARY_LENGTH)}", buildContent(commitsForIssue), issue)
      def buildContent(commits) {
      			.map { commit ->
      				"<p>Commit: <b>${}</b> &middot; Branch: <b>${StringEscapeUtils.escapeHtml4(commit.branch)}</b> &middot; Author: <b>${StringEscapeUtils.escapeHtml4(}</b></p><blockquote>${StringEscapeUtils.escapeHtml4(commit.message).replace('\n', '<br>')}</blockquote><p><b>${commit.files.size} file${commit.files.size != 1 ? 's' : ''}</b> changed:</p><ul>"

      						.map { file -> "<li><code style='color:${file.action.color}'>${file.action.label}</code> ${StringEscapeUtils.escapeHtml4(file.path)}</li>" }
      def postActivity(author, title, content, issue) {
      	def userProfile = new UserProfile.Builder(author.username).fullName(author.displayName).build()
      	def activityBuilder = new Activity.Builder(application, dateTime, userProfile)
      	if (issue) { ActivityObject.Builder().url(some(URI.create(issue.key))).build().right().get()))
      	Either<ValidationErrors, Activity> result =
      	for (Activity activity : result.right()) {
      		try {
      		} catch (Exception e) {
      			auditLog.error(e.message ?: "${e} ${e.stackTrace}")
      	for (ValidationErrors errors : result.left()) {
      		auditLog.error("Failed to post activity: " + errors.toString())
      • This will create separate a separate activity for each commit and for each issue mentioned in that. It means that if there is a commit linked to 3 issues, then in the global activity stream it will 3 (somewhat redundant) activities, but it also means that the same commit will appear in the activity stream of the issues.
      • Those strings that can contain HTML-unsafe characters should be HTML-encoded with StringEscapeUtils.escapeHtml4(htmlUnsafeString).
      • Those strings that can contain multi-line strings should be transformed with replace('\n', '<br>').
  7. Click Save.
  8. Name your automation rule intuitively, and click Turn it on.


  1. Create a commit in your repository with this message:
    Implement the FOO-1 feature.
  2. Then another with this:
    Fix the FOO-2 bug.
  3. Two activities will be created in the Jira activity stream with details of the related commit. Also, the corresponding activity will show up on the Activity tab of the linked issue.


If you don't get the expected results:

  1. See the general troubleshooting steps.


Ask us any time.