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

Append Content to page with Scriptrunner: size limits with getBodyAsString() & setBodyAsString()?

ABoerio May 6, 2019

Hi, 

I've managed to append content to an existing page in Confluence by scriptrunner console.

Below a sample of the code I'm using, which I know it's far from being an elegant programming example, :-), but in the end it works, and I'm using to introduce text, tables, etc...

//Libraries
import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.spaces.Space
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.confluence.pages.Attachment
import com.atlassian.confluence.pages.AttachmentManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.core.DefaultSaveContext

//Defining the management objects
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)

//Defining the page to update (the page already exists)
def outputPage=pageManager.getPage("~BOERIO", "Output")
//Get the current content of the page
def contentEntityObject = outputPage.getContentEntityObject()
def pageBody=outputPage.getBodyAsString()

// I want to add this text to the page
String appendedText="<p>Hello Confluence Page!</p>"

//Building the string with the new content
String outputPageBody=pageBody+appendedText

//Assigning the new content to the page
contentEntityObject.setBodyAsString(outputPageBody)

//Saving the page
pageManager.saveContentEntity(outputPage, DefaultSaveContext.DEFAULT)

//return outputPageBody

My question is related to the risk to reach a size limit for the string I'm building and assigning as new page content. Will I reach the point where the current page content is no more manageable with a string?

Is there a better approach to follow?

Thanks in advance for any suggestion.

Ciao, Andrea

 

1 answer

1 accepted

Suggest an answer

Log in or Sign up to answer
0 votes
Answer accepted
Ramakrishnan Srinivasan
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
April 24, 2020

Hi Andrea,

Thank you, because of the script you provided, I could get started.

 I am sure you would have found an answer by this time for your question,

still if I can share my views on this topic for others who might visit here, due the maximum string limitation (65536 characters is my understanding) which hits (I tested it with a large string and it throws error) the following are my attempts. All these are assuming ScriptRunner is installed in Confluence and JIRA and those apps are connected through Applink

Also I did not use xml, directly formed the page contents as strings

  • Create a REST END point in confluence to get page id by passing space key and page title 
  • Create a REST END point in confluence to append a page using your script above and REST END point documentation
    • parameters are space key, parent page title and append string
  • Create a new page with limited content using "rest/api/content". This is considered as a parent page
  • Get the newly created parent page id
  • Create one or more child pages to this parent page with ancestors page id pointing to the new parent page id
  • Use storage format of include macro to include a child page in parent
  • Create a string which takes all the child page include macros  - this string looks smaller to me
    • I found that you don't have to have the macro id parameter and version number and all in the string formation
  • call the append page REST END point and pass the above 
  • Also using the above confluence REST END points, I could use the script from JIRA ScriptRunner console too
    • In addition I could set jira macro to show the jira id used to create the page, Page Tree macro storage format is used to show page tree and toc macro to show table of contents
    • I am planning to set this in a issue workflow post function to create a large contents page

The caveat is when user views the page full content is visible but during edit, parent/child pages have to be edited separately

This is my trial attempt and worked for me, not sure any other things will hit later

 

Hope it helps others,

with warm regards

ramki

ABoerio May 7, 2020

Hi Ramki,

thanks for your feedback. In case of issues with content size I'll consider it.

actually I have started to use StringWriter, so a sample code is the following (it's still ugly, but it works).

 

import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.spaces.Space
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.confluence.pages.Attachment
import com.atlassian.confluence.pages.AttachmentManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.core.DefaultSaveContext
import groovy.xml.MarkupBuilder
import java.math.RoundingMode
import java.time.LocalTime

//needed this to monitor execution time
def inizio=LocalTime.now()
log.warn('Inizio:'+ inizio)

//general objects definition
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)

//decided to use StringWriter()
def writer2 = new StringWriter()
def xml2 = new MarkupBuilder(writer2)

//Define the target page (I know this page e)
def outputPage=pageManager.getPage("~BOERIO", "Statistiche Pagine Spazi")
def contentEntityObject = outputPage.getEntity()

//List all spaces in our instance
def spaziOMT=spaceManager.allSpaces.findAll{it.isGlobal()}
def quantiSpazi=spaziOMT.size()
log.warn('Spazi considerati:'+quantiSpazi)

xml2.p{
h3("Numero totale spazi: "+ quantiSpazi.toString())
}

//Create table
xml2.table(class: "aui") {
//table head
thead {
tr {
//Column titles
th('#Riga')
th('#Spazio')
th('Titolo Spazio')
th('#Pagina Spazio')
th('Titolo Pagina')
th('Id Pagina')
th('Ultimo Aggiornamento')
th('Numero Versioni')
th('Dimensioni Pagina')
th('N° Allegati (Ultime Rev)')
th('Peso Allegati (Ultime Rev)')
th('N° Vecchie Rev. Allegati')
th('Peso Allegati (Vecchie Rev)')
}
}

// counters
int rowNumber=0
int spaceNumber=0
int pageNumberInSpace=0

long MEGABYTE = 1024 * 1024

def spaziOMTCheck=spaziOMT


//table body/rows
tbody{
spaziOMTCheck.each(){
spazio ->
spaceNumber=spaceNumber+1
pageNumberInSpace=0

def pagineOMT=pageManager.getPages(spazio, true).findAll {
it instanceof Page}
def start=LocalTime.now().toSecondOfDay()

pagineOMT.each(){
pagina ->
rowNumber=rowNumber+1
pageNumberInSpace=pageNumberInSpace+1
tr{
td(rowNumber)
td(spaceNumber)
td(spazio.name)
td(pageNumberInSpace)
td{
a(href:'https://wiki-omt.mindmercatis.com'+pagina.getUrlPath()){
p(pagina.title)
}
}
td(pagina.id)
td(pagina.lastModificationDate.format("dd/MM/yy"))
td(pagina.version)
td(pagina.getBodyAsString().length())

def quantiAllegati=attachmentManager.countLatestVersionsOfAttachments(pagina)
def tutteVersioniAllegati=attachmentManager.getAllVersionsOfAttachments(pagina)
def vecchieVersioniAllegati=tutteVersioniAllegati.size()-quantiAllegati

long pesoUltimeRevAllegati=0
long pesoVecchieRevAllegati=0
tutteVersioniAllegati.each(){
versioneAllegato ->
if (versioneAllegato.isIndexable()){
pesoUltimeRevAllegati=pesoUltimeRevAllegati+versioneAllegato.fileSize
}
else {
pesoVecchieRevAllegati=pesoVecchieRevAllegati+versioneAllegato.fileSize
}
}

td(quantiAllegati)
double pesoUltimeRevAllegatiDB=pesoUltimeRevAllegati/MEGABYTE
BigDecimal pesoUltimeRevAllegatiDB2 = new BigDecimal(pesoUltimeRevAllegatiDB).setScale(2, RoundingMode.HALF_UP)
td(pesoUltimeRevAllegatiDB2)
//td(pesoUltimeRevAllegati)

td(vecchieVersioniAllegati)
double pesoVecchieRevAllegatiDB=pesoVecchieRevAllegati/MEGABYTE
BigDecimal pesoVecchieRevAllegatiDB2 = new BigDecimal(pesoVecchieRevAllegatiDB).setScale(2, RoundingMode.HALF_UP)
td(pesoVecchieRevAllegatiDB2)
//td(pesoVecchieRevAllegati)

}
}
def tempoSpazio=LocalTime.now().toSecondOfDay()-start
log.warn('Secondi:'+tempoSpazio+ ' Pagine:'+pagineOMT.size()+' Spazio:'+spazio.name)
}
}
}

def fineWriter=LocalTime.now()
log.warn('Fine Writer:'+fineWriter)

def totaleWriter=fineWriter.toSecondOfDay()-inizio.toSecondOfDay()
log.warn('Secondi impegati sino al Writer:'+totaleWriter)

xml2.p{
h3("Lavoro completato in "+totaleWriter+" secondi.")
}

//scrivo nel corpo della pagina la stringa costruita //salvo modifica alla pagina
contentEntityObject.setBodyAsString(writer2.toString())
pageManager.saveContentEntity(outputPage, DefaultSaveContext.DEFAULT)

def fine=LocalTime.now()
log.warn('Fine Script:'+fine)

def totale=fine.toSecondOfDay()-inizio.toSecondOfDay()
log.warn('Secondi impegati sino a fine script:'+totale)

return writer2

 I use pretty much the same approach when trying to send reports from Jira (in this case by REST API calls, checking existing version of the page, etc...).

 

Ciao, Andrea

Ramakrishnan Srinivasan
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
May 7, 2020

Hi Andrea,

 Thank you for the response,  with further learning I had on this subject

actually it is not required to create child pages and all. With your script as the base, developed Confluence REST END point given below. So, the variable 

String outputPageBody = pageBody + appendedText

does not look like limited by the string length 65536. I am not sure whether there is any upper limit when sending appendText string as url parameter.

In my calling script I set multiple string variables for each section of the wiki page.

Then I set one master string variable concatenating each of those string variables

def masterStringVariable_toAppend = sec1_str + sec2_str + sec3_str ...

then I am able to call this REST END point and the page is created.

masterStringVariable_toAppend length in my case is 104521 characters

 

/*import org.apache.log4j.Level
import org.apache.log4j.Logger
def log = Logger.getLogger("com.onresolve.jira.groovy")
log.setLevel(Level.DEBUG)*/

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

import com.atlassian.confluence.setup.settings.SettingsManager

import com.atlassian.confluence.pages.Attachment
import com.atlassian.confluence.pages.AttachmentManager

//Libraries
import com.atlassian.confluence.pages.Attachment
import com.atlassian.confluence.pages.AttachmentManager

//https://scriptrunner.adaptavist.com/latest/confluence/macros/CustomMacros.html#_including_all_child_pages
//https://community.atlassian.com/t5/Adaptavist-questions/Append-Content-to-page-with-Scriptrunner-size-limits-with/qaq-p/1075598
//https://community.atlassian.com/t5/Marketplace-Apps-Integrations/Script-runner-post-function-to-create-confluence-page-FROM/qaq-p/842469

import com.atlassian.confluence.xhtml.api.MacroDefinition
import com.atlassian.confluence.xhtml.api.XhtmlContent
import com.atlassian.renderer.v2.RenderUtils
import groovy.xml.MarkupBuilder
import groovy.json.JsonBuilder

@BaseScript CustomEndpointDelegate delegate

//End point name is the same as the method's name
append_rn_page_by_post(httpMethod: "POST") { MultivaluedMap queryParams, String body ->

//Defining the management objects
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)

//https://community.atlassian.com/t5/Marketplace-Apps-Integrations/Script-runner-post-function-to-create-confluence-page-FROM/qaq-p/842469
//def spaceKey = queryParams.getFirst("space").toString()
//def title = queryParams.getFirst("title").toString()
//log.debug("$spaceKey $title")
def mapper = new ObjectMapper()
def params = mapper.readValue(body, Map)
assert params.title // must provide title for the page
assert params.spaceKey // must provide spaceKey like MCDE

def spaceKey = params.spaceKey.toString()
def title = params.title.toString()
//log.debug("$spaceKey $title")

def pageIdMap = [:]
pageIdMap["pageId"] = 0
pageIdMap["Exception"] = "None"
pageIdMap["Invalid"] = "None"

def outputPage

if(spaceKey && title) {
//Defining the page to update (the page already exists)
try {
outputPage=pageManager.getPage(spaceKey, title)
pageIdMap["pageId"] = outputPage.id
//Get the current content of the page
def contentEntityObject = outputPage.getContentEntityObject()
def pageBody=outputPage.getBodyAsString()

// I want to add this text to the page
//String appendedText="<p>Hello Confluence Page!</p>"
String appendedText = params.appendContent

//Building the string with the new content
String outputPageBody = pageBody + appendedText

//Assigning the new content to the page
contentEntityObject.setBodyAsString(outputPageBody)

//Saving the page
pageManager.saveContentEntity(outputPage, DefaultSaveContext.DEFAULT)
} catch(Exception ex) {
pageIdMap["Exception"] = "Exception to get page or append page for spaceKey = ${spaceKey} and title = ${title}: ${ex.toString()}"
}

} else {
pageIdMap["Invalid"] = "Invalid spaceKey = ${spaceKey} or title = ${title}"
}
//return outputPageBody

//return Response.ok().build()
return Response.ok(new JsonBuilder(pageIdMap).toString()).build()
   

 

 with warm regards

ramki

Like ABoerio likes this
TAGS
AUG Leaders

Atlassian Community Events