Comment on Jira Issue for Linked Confluence Pages

Overview

Automatically comment on a Jira issue whenever the linked Confluence page gets updated.

Example

I am a project manager and have linked my Confluence and Jira instances. I have also linked some Jira issues to some Confluence pages.

When a Confluence page is updated with more information, I want the developer assigned to the linked issues to be notified. I can use this script as an Event Listener in ScriptRunner for Confluence to automatically post a comment on the Jira issue when the linked Confluence page is updated, thus notifying the assigned developer of any changes.

Good to Know

  • This script requires that you have ScriptRunner for Jira installed on your Jira instance and ScriptRunner for Confluence installed on your Confluence instance. Features from both plugins are utilized.
  • This script should be configured as an Event Listener in Confluence.
  • This script assumes that your Jira & Confluence instances have matching user directories. You will also need a service account on your Jira instance with administrator privileges as noted in the code comment around line 30.
  • The PageUpdatedEvent could be used to get more information about the update that could be written into the comment body.
  • This script uses the Remote Control feature of ScriptRunner to run code in the remote Confluence instance.
  • The code that runs in Jira relies on the ScriptRunner for Jira JQL function linkedIssuesOfRemote to find the Jira issues linked to the updated Confluence page.
  • An alternative configuration to suit the same need would be to create a REST Endpoint in Jira and call that from an Event Listener in Confluence. That would require maintaining two scripts on two systems, but individually, they might be simpler to reason about, and would remove the need for the @Grape annotations. Which approach you prefer is up to you!

Requirements

Jira Jira (8.0 - 8.6)

Confluence Confluence (7.1 - 7.6)

    
@Grapes([ @Grab("com.atlassian.jira:jira-api:8.0.0"), /* Many transitive dependencies of the Jira API will either be provided by the host Confluence application or simply aren't needed for this script. Since several of them won't be resolvable with the default configuration, we exclude them with the below annotations. */ @GrabExclude("com.atlassian.annotations:atlassian-annotations"), @GrabExclude("jta:jta"), @GrabExclude("log4j:log4j"), @GrabExclude("webwork:pell-multipart-request"), @GrabExclude("org.codehaus.jackson:jackson-core-asl"), @GrabExclude("org.codehaus.jackson:jackson-mapper-asl"), @GrabExclude("com.atlassian.sal:sal-api"), @GrabExclude("com.atlassian.gadgets#atlassian-gadgets-api"), ]) import com.atlassian.confluence.user.AuthenticatedUserImpersonator import com.atlassian.confluence.user.AuthenticatedUserThreadLocal import com.atlassian.jira.bc.issue.comment.CommentService import com.atlassian.jira.bc.issue.comment.CommentService.CommentParameters.CommentParametersBuilder import com.atlassian.jira.bc.issue.search.SearchService import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.jql.parser.JqlQueryParser import com.atlassian.jira.web.bean.PagerFilter import com.atlassian.sal.api.component.ComponentLocator import com.atlassian.user.UserManager import com.onresolve.scriptrunner.remote.RemoteControl import com.atlassian.jira.user.util.UserManager as JiraUserManager def page = event.page def (title, pageId, spaceKey) = [page.title, page.id, page.space.key] def currentUserKey = AuthenticatedUserThreadLocal.get().name log.debug "Current user is ${currentUserKey}" def serviceAccountUser = ComponentLocator.getComponent(UserManager).getUser('serviceaccount') //change this to a user with admin permissions on your remote Jira instance def messages = AuthenticatedUserImpersonator.REQUEST_AGNOSTIC.asUser({ RemoteControl.forPrimaryJiraAppLink().exec { log.debug "Beginning attempt to create comment in Jira" def commentService = ComponentAccessor.getComponent(CommentService) def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser) def searchService = ComponentAccessor.getComponent(SearchService) def user = ComponentAccessor.getComponent(JiraUserManager).getUserByName(currentUserKey) log.debug "Will seearch & create comments as ${user.name}" def jql = "issueFunction in linkedIssuesOfRemote('query', 'pageId=${pageId}')" log.debug "Searching with JQL query ${jql}" def query = jqlQueryParser.parseQuery(jql) def results = searchService.search(user, query, PagerFilter.unlimitedFilter) def issues = results.results issues.collect { issue -> log.debug "Found issue ${issue.key}; attempting to create comment as ${user.name}" def commentParameters = new CommentParametersBuilder() .author(user) .body("Page ${title} has been updated") // Customize this comment to contain the information you need .issue(issue) .build() def commentCreateValidationResult = commentService.validateCommentCreate(user, commentParameters) if (commentCreateValidationResult.isValid()) { def comment = commentService.create(user, commentCreateValidationResult, true) def message = "Created comment ${comment.id} on issue ${issue.key} in repsonse to update of page '${title}' in the ${spaceKey} space" log.debug message return message } log.error "Could not create comment as ${user.name}" commentCreateValidationResult.errorCollection.errorMessages.each { log.error it } commentCreateValidationResult.warningCollection.warnings.each { log.warn it } } } }, serviceAccountUser) messages.each { log.debug messages }
Discovered an issue? Report it here