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?
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()
}
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Helped a lot, thanks
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
@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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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)
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
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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 .
Kind regards
Sushma
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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. :)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.