This tutorial will show you a complex example on how to move data from JIRA Service Desk to Confluence.

Use Cases:

RCA analysis usually needs a sharing of information and document collaboration that is very hard to achieve in JIRA. That’s why a Confluence integration is a better idea for this particular example.


We need to setup a "Custom Scripted Function". Also, in order for this to work, you must have a reciprocal appLink between your Confluence and JIRA.

1. Set up the condition:

a): the request type is the one pertinent to us. In this case "Incident"

b): the transition to closed with a resolution that implies that you are going to do something with that ticket. In this case "Bug Reproduced"

2. Use this script to copy the information to Confluence.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.servicedesk.api.organization.OrganizationService
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.opensymphony.workflow.WorkflowContext
import groovy.json.JsonBuilder
import groovy.xml.MarkupBuilder


/*Fetch and check for the confluence link*/
def applicationLinkService = ComponentAccessor.getComponent(ApplicationLinkService)
def confluenceLink = applicationLinkService.getPrimaryApplicationLink(ConfluenceApplicationType.class)

/*If your issue isn't an incident, you don't want to create a RCA ticket*/
if ( != "Incident") {

/*Check that the confluence link exists*/
if (!confluenceLink) {
    log.error "There is no confluence link setup"

def authenticatedRequestFactory = confluenceLink.createAuthenticatedRequestFactory()

/*Start getting information about the issue from Service desk*/
def issueLinkManager = ComponentAccessor.getIssueLinkManager()
def commentManager = ComponentAccessor.getCommentManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def organizationService = ComponentAccessor.getOSGiComponentInstanceOfType(OrganizationService)

/*SLA related fields*/
def timeFirstResponse = issue.getCustomFieldValue(customFieldManager.getCustomFieldObjectByName("Time to first response"))
def timeResolution = issue.getCustomFieldValue(customFieldManager.getCustomFieldObjectByName("Time to resolution"))
def organizations = issue.getCustomFieldValue(customFieldManager.getCustomFieldObjectByName("Organizations"))

def currentUserId = ((WorkflowContext) transientVars.get("context")).getCaller()
def currentUser = ComponentAccessor.getUserManager().getUserByKey(currentUserId)

def writer = new StringWriter()
def xml = new MarkupBuilder(writer)

xml.h2( "This is the RCA analysis thread for the issue above.")


xml.p("This issue was raised by ${} on ${issue.getCreated()} " +
    "and resolved by ${} with resolution ${issue.getResolution().name}")

xml.h3("Time to first response:")
xml.p("Start date: ${timeFirstResponse.completeSLAData?.startTime[0].toDate().toString()}")
xml.p("Stop  date: ${timeFirstResponse.completeSLAData?.stopTime[0].toDate().toString()}")

xml.h3("Times to resolution:")
xml.p("Start date(s): ${timeResolution.completeSLAData?.startTime[0].toDate().toString()}")
xml.p("Stop  date(s): ${timeResolution.completeSLAData?.stopTime[0].toDate().toString()}")


//You might want to log information about your users and organizations.
organizations?.each {
    def usersEither = organizationService.getUsersInOrganization(currentUser, organizationService.newUsersInOrganizationQuery().customerOrganization(it).build())
    if (usersEither.isLeft()) {
        log.warn usersEither.left().get()
    usersEither.right().get().results.collect {"${it.displayName}"}.each{

//You want to collect the outward links of your issue.
def outwardLinks = issueLinkManager.getOutwardLinks(
xml.h3("Outward Issue Links")
if(outwardLinks instanceof List) {
    outwardLinks?.collect { buildIssueURL(it.destinationObject.key) }?.join(" ").each {

//You want to collect the inward links of your issue.
def inwardLinks = issueLinkManager.getInwardLinks(
xml.h3("Inward Issue Links")
if(inwardLinks instanceof List) {
    inwardLinks?.collect { buildIssueURL(it.destinationObject.key) }?.join(" ").each {

//You might also want to collect the comments on the issue:
commentManager.getComments(issue)?.collect { "${it.getAuthorFullName()} : $it.body"}.each{

//Here you parse the whole of the information collected into the RCA ticket.
def params = [
    type: "page",
    title: "RCA analysis: ${issue.key}",
    space: [
        key: "TEST" //This should be the name of your space, you should set it accordingly
    body: [
        storage: [
            value: writer.toString(),
            representation: "storage"
//This is used to send a REST request to the Confluence link.
    .createRequest(Request.MethodType.POST, "rest/api/content")
    .addHeader("Content-Type", "application/json")
    .setRequestBody(new JsonBuilder(params).toString())
    .execute(new ResponseHandler<Response>() {
    void handle(Response response) throws ResponseException {
        if (response.statusCode != HttpURLConnection.HTTP_OK) {
            throw new Exception(response.getResponseBodyAsString())

//This is an aux function to build the URL for the issue.
String buildIssueURL (String issueKey) {
    def baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL)
    <a href="$baseUrl/browse/$issueKey">$issueKey</a>

For how-to questions please ask on Atlassian Answers where there is a very active community. Adaptavist staff are also likely to respond there.

Ask a question about ScriptRunner for JIRA, for for Bitbucket Server, or for Confluence.