howto call a groovy code from Scriptrunner for Confluence macro
Asked by Carsten Hilber:

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?


Answers:
author: Jonny Carter
body: So, there are a few things you might be asking; I'll try to address each.

Per [the documentation on custom macros](https://scriptrunner.adaptavist.com/latest/confluence/macros/CustomMacros.html), 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](https://scriptrunner.adaptavist.com/latest/confluence/rest-endpoints.html), which is a separate piece of configuration from your macro.

Does that make sense?

comments: author: Jonny Carter
body: 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()
}
```


author: Carsten Hilber
body: Helped a lot, thanks


author: Santosh Subramani
body: Hello [Carsten Hilber](https://community.atlassian.com/t5/user/viewprofilepage/user-id/518706),

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.


author: Sushma
body: Hi [@Jonny Carter](/t5/user/viewprofilepage/user-id/781607) [@Carsten Hilber](/t5/user/viewprofilepage/user-id/518706)

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


author: Jonny Carter
body: [@Sushma](/t5/user/viewprofilepage/user-id/1632516)- 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](https://developer.mozilla.org/en-US/docs/Tools/Web_Console). I'd also recommend looking in your browser's [network tab](https://developer.mozilla.org/en-US/docs/Tools/Network_Monitor) 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.


author: Sushma
body: Hi [@Jonny Carter](/t5/user/viewprofilepage/user-id/781607)

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  
\]

<br />

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](https://community.atlassian.com/t5/image/serverpage/image-id/67005i9D52125A750BF856/image-size/large?v=v2&px=999 "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

<br />

@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](https://community.atlassian.com/t5/image/serverpage/image-id/67009iDB92BA8C667B0752/image-size/large?v=v2&px=999 "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


author: Sushma
body: Hi [@Jonny Carter](/t5/user/viewprofilepage/user-id/781607)

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](https://community.atlassian.com/t5/image/serverpage/image-id/67012iE721B867F5AC6D3E/image-size/large?v=v2&px=999 "script macro.PNG")

Kind regards

Sushma


author: Sushma
body: Hi [@Jonny Carter](/t5/user/viewprofilepage/user-id/781607)

Any workaround or suggestion ?

Regards

Sushma


author: Jonny Carter
body: That helps a lot, [@Sushma](/t5/user/viewprofilepage/user-id/1632516), 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](https://scriptrunner.adaptavist.com/latest/jira/remote-events.html) 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. :)


author: Sushma
body: Hi [@Jonny Carter](/t5/user/viewprofilepage/user-id/781607)

Thanks for your valuable suggestion and support.

Regards

Sushma


Tags: macro

scriptrunner