Adding nested macros to confluence page via API (ScriptRunner)
Asked by Jordan Hauser:

Hi community,  

Just posting a question here since I've been having trouble finding resources for this.  

While using scriptrunner to create a page, I can add macros to it. I do it through XML like this:

```
xml.'ac:structured-macro'('ac:name':"section) {
    'ac:rich-text-body'("here are some values to be shown in the body of the macro")
}
```

but keep running into problems when I try to add a macro within the body of that first macro. Can someone tell me what I'm missing here or point me at some documentation? Not sure how to bypass the 'WYSIWYG' editor many of these macro's have. Here is an example of one attempt of mine:

```
xml.'ac:structured-macro'('ac:name':"section") {
    'ac:inline'('ac:structured-macro'('ac:name':"column") {
        'ac:rich-text-body'("there should be some value in here")
    })
}
```

EDIT: Found during testing and looking at other documentation pages that you can specify 'inline' instead of 'rich-text-body' or 'plain-text' and it will read a little bit of the code. It was just reading the string value "there should be some value in here" instead of just "ac:structured-macro". Still not invoking the macro to be nested in the section macro.


Answers:
author: Jordan Hauser
body: Posting the solution here. Bear in mind I'm working with groovy in the scriptrunner console in Jira, and not a python file to run on the backend with a curl request:  

-Create the page  
-Retrieve the page body in storage format. It will be in an XHTML document object.  
-Make necessary alterations to the document object  
-execute PUT request - see below  

Resources that were very helpful!  
<https://docs.atlassian.com/ConfluenceServer/rest/7.15.0/#api/content-update>  
[Document (jsoup Java HTML Parser 1.15.2-SNAPSHOT API)](https://jsoup.org/apidocs/org/jsoup/nodes/Document.html) also see 'Element'  
And many more forum posts that may not have been directly helpful but gave me a better idea of what I was looking for  

```
def params = [

    type : "page",

    title: pageTitle,     // <------- grab the original page title if not changing or specify a new one

    version: [

        number: "2"          // <----- I hard incremented during testing. Grab the original page version and + 1 it        

    ],

    status: "current",        // <------------ you can make it a draft if you so please

    body : [

        storage: [

            value         : new_bod,    //<-------- literally just take the modified xhtml document and .toString() it

            representation: "storage"

        ]

    ],

]
```

All for this:  
![Web capture_14-6-2022_132936_confluence.wilcoxind.com.jpeg](https://community.atlassian.com/t5/image/serverpage/image-id/201901i00B2063E4736C9E0/image-size/large?v=v2&px=999 "Web capture_14-6-2022_132936_confluence.wilcoxind.com.jpeg")  

I was a bit frustrated you can't just create both the macros at the same time, although now I may try just formatting the page in XHTML in the first place instead of going through an xml writer in the script to create a page initially. If I succeed I will post more info here.


author: Stefan Salzl
body: Hi [@Jordan Hauser](/t5/user/viewprofilepage/user-id/4475896)

In your original post you mentioned you are able to embed a macro while creating a page via xml.

Could you please provide an example of code how this would work?

I got stuck on how to add the xml/macro content to the page content:

```
import com.atlassian.confluence.pages.Page

import com.atlassian.confluence.spaces.SpaceManager

import com.atlassian.confluence.pages.PageManager

import com.atlassian.sal.api.component.ComponentLocator

def pageManager = ComponentLocator.getComponent(PageManager)

def spaceManager = ComponentLocator.getComponent(SpaceManager)

def spaceKey = "STS"

def space = spaceManager.getSpace("${spaceKey}")

def homePage = space.getHomePage()

 

Page parentPage = pageManager.getPage(homePage.id);

Page page = new Page();

page.setSpace(space);

page.setParentPage(parentPage);

page.setTitle("Created by Scriptrunner");

page.setBodyAsString('');

parentPage.addChild(page);

pageManager.saveContentEntity(page, null, null);
```

This is where I am. Tried to add the macro as string but didn?t work.  
Any help appreciated.  
Best  
Stefan

comments: author: Jordan Hauser
body: This appears to be the Confluence api, for this post I was generating the page from our Jira instance. Basically I created the confluence page template manually, then copied the page source into my code and edited certain lines to use variables instead of whatever was in the template.  

Whatever you pull from the template page source, try pasting that into the 'page.setBodyAsString('')' function  

I tested what you posted in my console and after editing the string page source stuff I had from my original script (basically just removed the variables I had in) and it worked just fine  

<br />

Here is an example you can paste in that spot, just replace the issue key with some one from your systems (shows up twice, I bolded and underlined them)  

```
"""<p class="auto-cursor-target">

    <ac:structured-macro ac:name="jira" ac:schema-version="1" ac:macro-id="02d7303d-bc19-42c7-b22a-0e8b3a1a5a7b">

        <ac:parameter ac:name="server">Jira</ac:parameter>

        <ac:parameter ac:name="columnIds">

            issuekey,summary,issuetype,created,updated,assignee,reporter,priority,status,resolution

        </ac:parameter>

        <ac:parameter ac:name="columns">

            Key,Summary,Type,Created,Updated,Assignee,Reporter,Priority,Status,Resolution

        </ac:parameter><ac:parameter ac:name="jqlQuery">key = **JTES-2256**</ac:parameter>

        <ac:parameter ac:name="serverId">

            08c4b063-58d0-3ea5-906a-60932f95f843

        </ac:parameter><ac:parameter ac:name="key">

            ++JTES-2256++

        </ac:parameter>

    </ac:structured-macro>

</p>

<p><strong>General Ticket Information</strong></p>

<p>Program: </p>

<p>Test Engineers: </p>

<p>Manager Approving: </p>

<p>Created: </p>

<p>Due: </p>

<p>Location: </p>

<p class="auto-cursor-target"><br /></p>

<p><strong>Table Of Contents</strong></p>

<p class="auto-cursor-target"><ac:structured-macro ac:name="toc" ac:schema-version="1" ac:macro-id="1e1c437a-eabb-4530-9862-661b0ba20b75" /></p>

<hr />

<h1>General</h1>

<h2>Purpose/Background</h2>

<p></p>

<p><br /></p>

<p><br /></p>

<hr />

<h1>Testing Info</h1>

<h2>Procedure</h2>

<h2>Required Materials and Equipment</h2>

<h2>Post Testing Info Comparison</h2>

<p><br /></p>

<p><br /></p>

<hr />

<h1>Post Testing Info</h1>

<h2>Additional Info</h2>

<h2>Test Results</h2>

<h2>Conclusion/Takeaways</h2>

<p><br /></p>

<p><br /></p>

<hr />

<h2>Attachments</h2>

<p><ac:structured-macro ac:name="attachments" ac:schema-version="1" ac:macro-id="5be11d15-d16e-49a2-9f57-f9a13f73bbd2" /></p>

<p class="auto-cursor-target"><br /></p>

<p class="auto-cursor-target"><br /></p>

<p><br /></p>

<p><br /></p>"""
```


author: Alexandra-Mihaela Apostol
body: Hi [@Jordan Hauser](/t5/user/viewprofilepage/user-id/4475896) ,

This is a very interesting idea! Can you please share with us an example on how you retrieve the storage format of the confluence page you are creating with ScriptRunner? I have a similar request but my solution is not working when I try to create the confluence page in one go with more macros.

Thank you!

comments: author: Jordan Hauser
body: Hi [@Alexandra-Mihaela Apostol](/t5/user/viewprofilepage/user-id/4981201)   

When I was updating the page at a later point in the workflow, I have to use a GET request for using the following endpoint:  

/plugins/viewstorage/viewpagestorage.action?pageId={page id you want}  

The request was structured like this:

//Outside the primary method I'm using in this project found in here [Interacting with Confluence from JIRA (adaptavist.com)](https://scriptrunner.adaptavist.com/latest/jira/interacting-with-confluence-from-jira.html)

//Most packages I used for this, might be missing a couple

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 org.jsoup.\* //This was needed to parse the xhtml

```
def getPrimaryConfluenceLink() {

def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService.class)

final ApplicationLink confLink = applicationLinkService.getPrimaryApplicationLink(ConfluenceApplicationType.class)

confLink

}
```

Within the method I created for this project, I had to do the GET request.  

```
(1)   confluenceLink.createAuthenticatedRequestFactory()

(2)        .createRequest(Request.MethodType.GET, """plugins/viewstorage/viewpagestorage.action?pageId=""" + holder)

(3)        .addHeader("Content-Type", "application/json")

(4)        .execute(new ResponseHandler<Response>() {

(5)            @Override

(6)            void handle(Response response) throws ResponseException {

(7)                if (response.statusCode != HttpURLConnection.HTTP_OK) {

(8)                    throw new Exception(response.getResponseBodyAsString())

(9)                } else {

(10)                    def xhtml_parser = Jsoup.parseBodyFragment(response.responseBodyAsString)

(11)                    xhtml_parser.outputSettings.prettyPrint(false)

                }       

            }       

        })
```

It uses the confluence application link and sets the structure for the request with the factory (1), then creates the GET request referencing the endpoint for the page storage and using a variable for the pageId which you could hardcode if your just doing one (2). I needed to add a header for the request type (3) and you may need to add in some auth if the instance is more secure using another .addHeader("Authorization", "{basic or bearer auth depending on what you need}").

Then it executes the request (4)and says to use a certain variant of the response handler class (5 and 6). 7 and 8 are what to do in case it fails, 9 tells it what to do when it succeeds. 10 is to define a variable thats the body of the page, parsed from the xhtml its formatted in using the jsoup library. 11 sets it to the working format for sending it back when I update the page, if you need to read it in the logs while testing I suggest setting prettyPrint(true) and converting it back to false later.

Hope that helps!

-Jordan


author: Alexandra-Mihaela Apostol
body: Hi [@Jordan Hauser](/t5/user/viewprofilepage/user-id/4475896) , this worked perfectly for me!

Thank you for your explanation!

Cheers,

Alexandra-Mihaela


Tags: confluence-server

macro

scriptrunner

server