Create Confluence pages from Jira issues - with links and data - in one click
Greetings Fellow Atlassian Enthusiasts! I think I have something that provided a lot of value to us, and I'd like to share with the community, so other people can make creating documentation from inside of Jira a Snap! It requires executing groovy on a Jira issue in transition, and I used Jira Misc Workflow Extensions to do it.
Over the past year, I got into Groovy scripting with Jira Misc Workflow Extensions (JMWE). This app has genuinely cranked our Jira workflow automation to the max. I keep finding more and more use cases for it in our company.
This particular use case of JMWE I am proud of the most: we are giving our users the ability to build Confluence pages for customer issues automatically, right from Jira.
We found that it is often hard to get users to leave Jira to create a Confluence page on an issue. To simplify the process, and ensure accuracy, we wanted to add a button right in Jira that would give users a way to generate a new Confluence page automatically, with all necessary issue data, and of course, link it back to that issue itself.
At this point, it is an early proof of concept in our company. However, our testing shows that it is working well and we’ll be expanding it out to all our users shortly. It is just one of our projects made possible with Jira Misc Workflow Extensions (JMWE). Below, I share the Groovy script we use. While the code might seem a bit complicated, I hope that people with some understanding of programming can make use of it.
Here is how we did it
First, we used the “global looping transition” released in Jira, so that the Transition can be executed from anywhere, at any time.
This transition will loop, meaning it will not change the Status. We used it to add a button to the issue detail view to create a confluence page as it requires no status changes. There's a great post about how to use this “global looping transition” on the Atlassian Community that has helped us understand how the concept works.
Then we made it more powerful and automated it with JMWE.
Here is what it looks like, The Jira issue is automatically linked to the Confluence page:
The Confluence page is automatically linked to the Jira issue.
The Code for this transition is below, just replace the values after "Equals"
Note: It was cleaned up as it is a bit complicated, but anyone with a basic understanding of programming should be able to make use of it.
If you don’t know how to get the Storage format for a Confluence page:
For jiraServerName and JiraServer here’s how to get your JIRA’s server ID:
https://confluence.atlassian.com/adminjiraserver/finding-your-server-id-938847652.html
Code (code macro hates me):
import org.apache.groovy.json.DefaultFastStringService
import org.apache.groovy.json.FastStringService
import org.apache.groovy.json.FastStringServiceFactory
import com.atlassian.applinks.api.ApplicationLink
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.application.confluence.ConfluenceApplicationType
import com.atlassian.jira.issue.Issue
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.sal.api.net.Request
import com.atlassian.sal.api.net.Response
import com.atlassian.sal.api.net.ResponseException
import com.atlassian.sal.api.net.ResponseHandler
import groovy.json.JsonBuilder
import groovy.json.JsonSlurper
import groovy.xml.MarkupBuilder
//Variables to set you only should have to configure Lines 19-31 you can even use Dynamic variables if you have them fielding from custom fields or something.
//This should be the Confluence Space key where the pages will be created.
def confSpace = "DEMOSPACE"
// This is the page ID of the confluence page that should be the parent of whatever article is produced.
def parentPageID = 010101010
// from your Application settings (this setting can be found in Confluence under application links)
def jiraServerName = "Jira Server"
//from your Jira server (this setting can be found in Confluence under application links)
def jiraServerID = "AAA-BBB-CCC-DDD"
//Will dictate the type of article, it should be mapped to a jira custom field.
def articleField = issue.getAsString("Custom Dropdown Field")
// In this example this is a 2 Article setup,
// for each article setup the articleType# and the option mapped to article Field (Above)
// for pageTitle# set up a dynamic name of the page, in this case it uses the Jira issue Summary to create a unique Name, note it will fail it you try to create a duplicate name
// for each confTemplate# grab the storage format from confluence after making a page, then you may substitute variables (like we did in the example) to make your pages more dynamic.
def articleType1 = "How-To Article"
def pageTitle1 = "How-To " + issue.summary
def confTemplate1 = '<ac:structured-macro ac:name="info" ac:schema-version="1" ac:macro-id="465846a6-25cb-46ab-a319-9a2c25752959"><ac:rich-text-body>'+ issue.description + '</ac:rich-text-body></ac:structured-macro>'
def articleType2 = "Incident Resolution"
def pageTitle2 = issue.summary + " Incident"
def confTemplate2 = issue.description + ' </ac:rich-text-body></ac:structured-macro><p><br /></p><h2>Steps to Resolve</h2><p><br /></p><h2>Related articles</h2><ac:structured-macro ac:name="info" ac:schema-version="1" ac:macro-id="465846a6-25cb-46ab-a319-9a2c25752959"><ac:rich-text-body>'+ issue.description + '</ac:rich-text-body></ac:structured-macro>'
// ---------- // You don't have to Edit below here, just change the settings above, unless you want to use more than 2 different articleTypes, then you must edit:
//Just setting these Values to something for Error handling
def finalTitle = "1"
def finalTemplate = "1"
// This section Retrieve the primary confluence application link defined above
def ApplicationLink getPrimaryConfluenceLink()
{
def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService.class)
final ApplicationLink conflLink = applicationLinkService.getPrimaryApplicationLink(ConfluenceApplicationType.class)
conflLink
}
def confluenceLink = getPrimaryConfluenceLink()
assert confluenceLink // must have a working app link set up
def authenticatedRequestFactory = confluenceLink.createImpersonatingAuthenticatedRequestFactory()
// Logic to Sort out Dynamic Template and Names, the users are asked what type of Article they are creating on the screen their answer sets the page name and template.
//--- NOTE if you want to use more than 2 article types, add a new elseif block and replace the 2's with 3's after making the section above match.
if(articleField == articleType1)
{
finalTitle = pageTitle1
finalTemplate = confTemplate1
}
else if(articleField == articleType2)
{
finalTitle = pageTitle2
finalTemplate = confTemplate2
}
// this Section creates the JIRA issue macro for the Page, linking them together.
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.'ac:structured-macro'('ac:name': "jira") {
'ac:parameter'('ac:name': "key", issue.key)
'ac:parameter'('ac:name': "server", jiraServerName)
'ac:parameter'('ac:name': "serverId", jiraServerID) // Replace with your Server ID for your instance
}
// this section links the JIRA Issue with the Defined confluence template
VALUE = writer.toString() + finalTemplate;
// This part Builds the JSON message from the pieces above.
def params = [
type : "page",
title: finalTitle,
space: [ key: confSpace ],
ancestors: [
[
type: "page",
id: parentPageID ,
]
],
body : [
storage: [
value : VALUE ,
representation: "storage"
]
]
]
// Make the connection Above
authenticatedRequestFactory
.createRequest(Request.MethodType.POST, "rest/api/content")
.addHeader("Content-Type", "application/json")
.setRequestBody(new JsonBuilder(params).toString())
.execute(new ResponseHandler<Response>() {
@Override
void handle(Response response) throws ResponseException {
if (response.statusCode != HttpURLConnection.HTTP_OK) {
throw new Exception(response.getResponseBodyAsString())
} else {
def webUrl = new JsonSlurper().parseText(response.responseBodyAsString)["_links"]["webui"]
}
}
})
I've been working with this for a couple of days. Is it possible to get this to work with Jira and Confluence Cloud? I can run the script successfully, but it never creates anything in Confluence.
Completely possible to do it in cloud, but I believe the API calls are completely different for Confluence/Jira Cloud -- we're not migrating to cloud till 2023 so I haven't written a new version yet.
But it is definitely possible.