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):
Configuration
- Login to Jira as admin, go to Administration → System → Automation rules.
- Click Create rule.
- Select the trigger Changeset accepted (from the DevOps category).
- Click Save.
- Click New action.
- Select the action Run Groovy script.
- Enter "Post activity" to the Description field.
- Enter this Groovy script:
@PluginClassLoader("com.atlassian.streams.streams-thirdparty-plugin") import static com.atlassian.streams.api.Html.html import static com.atlassian.streams.api.common.Option.some import java.util.stream.Collectors 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.jira.config.properties.APKeys 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) { return } application = Application.application('Better DevOps Automation', URI.create('https://www.midori-global.com/products/better-devops-automation-for-jira')) 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!) .orElse(devops.initiator) 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) { return commits.stream() .map { commit -> "<p>Commit: <b>${commit.id}</b> · Branch: <b>${StringEscapeUtils.escapeHtml4(commit.branch)}</b> · Author: <b>${StringEscapeUtils.escapeHtml4(commit.author)}</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>" .concat( commit.files.stream() .map { file -> "<li><code style='color:${file.action.color}'>${file.action.label}</code> ${StringEscapeUtils.escapeHtml4(file.path)}</li>" } .collect(Collectors.joining())) .concat('</ul>') } .collect(Collectors.joining()) } 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) .id(some(URI.create("${applicationProperties.getString(APKeys.JIRA_BASEURL)}/${dateTime}/${issue?.key}"))) .title(some(html(title))) .content(some(html(content))) if (issue) { activityBuilder.target(some(new ActivityObject.Builder().url(some(URI.create(issue.key))).build().right().get())) } Either<ValidationErrors, Activity> result = activityBuilder.build() for (Activity activity : result.right()) { try { activityService.postActivity(activity) } catch (Exception e) { auditLog.error(e.message ?: "${e} ${e.stackTrace}") } } for (ValidationErrors errors : result.left()) { auditLog.error("Failed to post activity: " + errors.toString()) } }
Notes:- 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>').
- Click Save.
- Name your automation rule intuitively, and click Turn it on.
Usage
- Create a commit in your repository with this message:
Implement the FOO-1 feature.
- Then another with this:
Fix the FOO-2 bug.
- 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.
Troubleshooting
If you don't get the expected results:
- See the general troubleshooting steps.
Questions?
Ask us any time.