Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Next challenges

Recent achievements

  • Global
  • Personal

Recognition

  • Give kudos
  • Received
  • Given

Leaderboard

  • Global

Trophy case

Kudos (beta program)

Kudos logo

You've been invited into the Kudos (beta program) private group. Chat with others in the program, or give feedback to Atlassian.

View group

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

howto call a groovy code from Scriptrunner for Confluence macro

I want to write a macro with Scriptrunner. It should place a button over a table. On pressing that button i want to read some of the fields of that table and send it to a REST Service.

I know how to place that button from the examples provided from Adaptavist. I also know how to write groovy code to have a function to feed the REST Service. 

I struggle where to put the groovy code, so that I can call it, when the button is pressed. Can sonone provide an example, please?

 

1 answer

1 accepted

1 vote
Answer accepted

So, there are a few things you might be asking; I'll try to address each.

Per the documentation on custom macros, there are three code blocks that define your macro.

The first takes some Groovy code that should return an HTML string. The HTML will be what ultimately renders on the page, and in your case, should probably include an <button> tag.

The second takes some JavaScript code. This is where you'd write the code that read the contents of your table and sent the data to an external REST service.

The third block is where you can put CSS, to style your macro.

If you're asking where you put the code for your REST Service, that would be in a custom REST Endpoint, which is a separate piece of configuration from your macro.

Does that make sense?

To expand a bit, the javascript to read your table might look something like this:

AJS.toInit(function () {
let buttonSelector = 'button#myCustomMacroButton';
let button = document.querySelector(buttonSelector);

function clickHandler(clickEvent) {
let tableElements = document.querySelectorAll(buttonSelector + ' ~ table td');
let tableData = [];
tableElements.forEach(function (tdElement) {
tableData.push(tdElement.innerHTML);
});
AJS.$.ajax({
type: "POST",
url : AJS.params.contextPath + "/rest/scriptrunner/latest/custom/myCustomEndpoint",
data: {
tableData: tableData
}
}).done(function (data, status, jqXhr) {
console.log("Retrieved response from custom rest endpoint: ");
console.log(data);
console.log(status);
});
}

button.addEventListener('click', clickHandler);
});

The handler function passed to "done" is what happens whenever your data comes back from the server. I assume you'd want something more than just logging the data to the console, but that's just an example.

Your REST Endpoint would need to receive that data and do something with it.

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.json.JsonBuilder
import groovy.transform.BaseScript
import org.codehaus.jackson.map.ObjectMapper
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response

@BaseScript CustomEndpointDelegate delegate

myCustomEndpoint(
httpMethod: "POST", groups: ["confluence-administrators"]
) { MultivaluedMap queryParams, String body ->

def mapper = new ObjectMapper()
def data = mapper.readValue(body, Map)

return Response.ok("Received data: $data").build()
}

Helped a lot, thanks 

Hello Carsten Hilber

Can you pass on the code of your request.

My requirement is to display Crowd user information in confluence page. 

Parameter is Jira Project Key. 

Rest api to get user information and display in a page.

Hi @Jonny Carter @Carsten Hilber 

I have a similar requirement, On clicking the "Button" it should trigger a REST api of jira Server and also it shoul send the body of entire page and  pageTitle.

is it possible to do that ?

I tried but, onclick() event is not triggering the REST API of jira

Please respond..

Thanks in advance 

Sushma

@Sushma- easiest way to debug it will probably be to put some console.log("<message") statements into your code at various points to see where execution stops and why using your browser's developer console. I'd also recommend looking in your browser's network tab to see if the call to the REST API ever gets made, and if it returns an error.

That's the best advice I can give you. If you still need help, maybe post your own question with a detailed description of what you're trying to do, why, and what code you're using.

Hi @Jonny Carter 

In jira post function I have added the below code to achieve the page creation in confluence (using page template) from jira server.

import com.atlassian.applinks.api.ApplicationLink
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.application.confluence.ConfluenceApplicationType
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
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.search.SearchProvider
import groovy.xml.MarkupBuilder

def ApplicationLink getPrimaryConfluenceLink() {
def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService.class)
final ApplicationLink conflLink = applicationLinkService.getPrimaryApplicationLink(ConfluenceApplicationType.class)
conflLink
}
HashSet<String> typecomponentset = new HashSet();
HashSet<String> typeflowset = new HashSet();
def confluenceLink = getPrimaryConfluenceLink()
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
assert confluenceLink // must have a working app link set up
CustomFieldManager customFieldManager = ComponentAccessor.customFieldManager
def issueManager = ComponentAccessor.getIssueManager()
def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)
def searchService = ComponentAccessor.getComponent(SearchService)
def user = ComponentAccessor.jiraAuthenticationContext.getLoggedInUser()
HashSet batchset = new HashSet();
def impcomponent = customFieldManager.getCustomFieldObjectByName("Impacted Technical Components")
def impcomponentvalues = issue.getCustomFieldValue(impcomponent)
log.info(impcomponentvalues)
impcomponentvalues.each { impcomponentvalue ->
Issue issue = impcomponentvalue as MutableIssue
if(issue.getIssueType().name == "Component") {
def typeofcomponent = impcomponentvalue.toString()
MutableIssue childissue = ComponentAccessor.getIssueManager().getIssueObject(typeofcomponent)
def cField = customFieldManager.getCustomFieldObjectByName("Type of Component")
def val = childissue.getCustomFieldValue(cField).toString()
typecomponentset.add(val)
}
else if(issue.getIssueType().name == "Flow") {
def typeofcomponent = impcomponentvalue.toString()
MutableIssue childissue = ComponentAccessor.getIssueManager().getIssueObject(typeofcomponent)
def cField = customFieldManager.getCustomFieldObjectByName("Type of Flow")
def val = childissue.getCustomFieldValue(cField).toString()
typeflowset.add(val)
}
else {
log.info("other technical component")
}
}
log.info(typecomponentset)
def query = jqlQueryParser.parseQuery("project = DD AND \"Type of Impacted Technical Component\" = Batch")
def search = searchService.search(user, query, PagerFilter.getUnlimitedFilter())
search.getIssues().each { documentIssue ->
def dod = issueManager.getIssueObject(documentIssue.id)
log.info(dod.summary)
batchset.add(dod.summary.toString())
}
def query1 = jqlQueryParser.parseQuery("project = DD AND \"Type of Impacted Technical Component\" = Interface")
def search1 = searchService.search(user, query1, PagerFilter.getUnlimitedFilter())
search1.getIssues().each { documentIssue1 ->
def dod1 = issueManager.getIssueObject(documentIssue1.id)
log.info(dod1.summary)
batchset.add(dod1.summary.toString())
}

int count = 0;
batchset.each { e ->
xml."ac:task-list"{
"ac:task"{
"ac:task-id"(count)
"ac:task-status"("incomplete")
"ac:task-body"(e.toString())
}
}
}

def params = [
title: "${issue.key}" + " : " + "${issue.summary}",    //page title
tamplateid : 12386309 , // template id
key : issue.key ,          //issue key
body : writer.toString()     // batch Dod's
]


def authenticatedRequestFactory = confluenceLink.createImpersonatingAuthenticatedRequestFactory()
authenticatedRequestFactory
.createRequest(Request.MethodType.POST, "/rest/scriptrunner/latest/custom/createPageFromTitle")
.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"]
}
}
})

In the above code ,I am passing 4 paramters:

1.page title

2.page template id

3.issue key

4. set of values that needs to be appear in the confluence page as checklist values (which is shown in the below screenshot)

button1.PNG

To receive the above parameters I created the REST endpoint in confluence using the below script.

import com.onresolve.scriptrunner.runner.rest.common.CustomEndpointDelegate
import groovy.transform.BaseScript
import org.codehaus.jackson.map.ObjectMapper
import com.atlassian.confluence.spaces.Space
import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.core.DefaultSaveContext
import com.atlassian.confluence.api.impl.sal.ConfluenceApplicationProperties
import javax.ws.rs.core.Response
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.pages.templates.PageTemplateManager

/*import com.atlassian.confluence.pages.templates.variables.Variable
import com.atlassian.confluence.pages.templates.PageTemplate*/

import javax.servlet.http.HttpServletRequest
import javax.ws.rs.core.MultivaluedMap
import javax.ws.rs.core.Response
import static com.atlassian.user.security.password.Credential.unencrypted


@BaseScript CustomEndpointDelegate delegate

import org.apache.log4j.Logger
import org.apache.log4j.Level
def log = Logger.getLogger("com.acme.workflows")
log.setLevel(Level.DEBUG)

def confluenceApplicationProperties = ComponentLocator.getComponent(ConfluenceApplicationProperties)
createPageFromTitle(
httpMethod: "POST") {MultivaluedMap queryParams, String body ->

def mapper = new ObjectMapper()
def params = mapper.readValue(body, Map)
assert params.key
assert params.title
assert params.tamplateid
assert params.body

//log.info(params.dod.getClass().getName())
def pageTemplateManager = ComponentLocator.getComponent(PageTemplateManager)
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
Space space = spaceManager.getSpace("TES")
Page HomePage = space.getHomePage()
def NewParentPage
try {
def pageTemplate = pageTemplateManager.getPageTemplate(params.tamplateid as Long)
String tamplateAsString = pageTemplate.getContent()
String issuekey = params.key
String pageTitle= params.title
NewParentPage = new Page(title: pageTitle, bodyAsString: tamplateAsString, space: space, parentPage: HomePage)
NewParentPage.setBodyAsString(tamplateAsString.replaceAll("batchset","${params.body}")) //here I am setting the body of the confluence page
pageManager.saveContentEntity(NewParentPage, DefaultSaveContext.DEFAULT)
HomePage.addChild(NewParentPage)
pageManager.saveContentEntity(HomePage, DefaultSaveContext.MINOR_EDIT)
} catch (e) {
return Response.serverError().entity([error: e.message]).build()
}

Response.ok().build()
}

My template format is as shown below

template structure.PNG

here what i am doing is I am replacing the "batchset" value with the "body" which is received from Jira.

Using the above code I am successfully able create a page in confluence.

But my concern is ,

If someone adds a new values to the batchset in jira I need to update the page with the newly added values.

So to update the page , I created a button on the confluence page. "My requirement is on clicking the "Refresh" button I want to update the existing confluence page with the newly added values ".

I am not getting any way to update the page" .

Do you have any idea to achieve it ?"

Please help me to resolve this bottleneck.

Thanks in advance 

Kind Regards

Sushma

Hi @Jonny Carter 

I am using script macro to create a button in confluence page.

On clicking the button it should call the REST api of jira server to update the existing confluence page with the new values .

script macro.PNG

Kind regards

Sushma

Hi @Jonny Carter 

Any workaround or suggestion ?

Regards

Sushma

That helps a lot, @Sushma, thanks! This is an interesting use case.

So, you could potentially do it as you are, using a scripted macro that adds a button to the page... when you click it, it refreshes.

A better idea, I think, would be one of two paths:

1. Create a script macro that simply pulls & renders the information you need from Jira on the fly. It's always up to date that way. The downside is, no one can make edits to that content, as they can with your current solution. Depending on your use case, that might be a benefit, though.

2. Create a listener in Jira that, on the IssueUpdated event, calls a separate REST endpoint in Confluence that updates the page, rather than creating a new one.

The second path will probably be the most straightforward to implement with your current setup. All you'd really need to change would be writing a REST endpoint in Confluence that updated the page, rather than creating one. That's pretty straightforward. You use the same PageManager.saveContentEntity method you used to create the page, you just need to pass it the page you want to update, rather than a new page object. Since you have a standard format for your page title, you could simply call pageManager.getPage("SPACEKEY", "Page title").

In fact, you could probably bypass writing the Listener in Jira & the REST Endpoint in ScriptRunner for Confluence entirely, and just create a Remote Event Listener in Confluence that listens for the linked Jira instance's Issue Updated event. That said, it might be quickest for you to start with the familiar tool of the REST endpoint.

Anyway, that's the rough outline. If you have some more questions about the details, feel free to ask. It would help to know a bit more about the why of this use case. I understand a comment thread can get a bit crowded, so if you need to start a new question where you lay out your use case from first principles, that's fine. You can @mention me. :)

Hi @Jonny Carter 

Thanks for your valuable suggestion and support.

Regards

Sushma

Like Jonny Carter likes this

Suggest an answer

Log in or Sign up to answer
TAGS
Community showcase
Posted in Confluence

What do you think is the most *delightful* Confluence feature? Comment for a prize!

- Create your own custom emoji 🔥 - "Shake for Feedback" on mobile 📱 - An endless supply of GIFs via GIPHY 🤩 Is there anything quite as nice as a pleasant surprise? Comment below with what...

415 views 23 8
Join discussion

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you