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

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

getting error while accessing key value from the json data in script runner confluence

Hi Team

I am trying to retrieve the key values from the JSON data in the confluence script runner.

but i am getting the below error.

groovy.json.JsonException: expecting '}' or ',' but got current char 's' with an int value of 115 The current character read is 's' with an int value of 115 expecting '}' or ',' but got current char 's' with an int value of 115 line number 9 index number 217 "value": "<at:declarations /><ac:structured-macro ac:name="section" ac:schema-version="1" ac:macro-id="8d7755f6-be87-4926-80d6-9561a0002bf0"><ac:rich-text-body><p><ac:task-list> .......................................................................^ at Script229.run(Script229.groovy:71)

 

Anyone please help me to resolve this.

Regards

Sushma

1 answer

1 accepted

0 votes
Answer accepted

Hi @Sushma ,

The issue might be related to the way you are reading / parsing the JSON. If possible, could you please share the JSON you are parsing and the method you are using to parse it?

You could attempt on running the following:

import groovy.json.JsonSlurper;
...
new JsonSlurper().setType(JsonParserType.LAX).parseText(index)

The important thing is to change the string format to correct JSON (e.g key with quotes) or you can change JsonSlurper's parser type as mentioned above.

Kind regards,
Rafael

Hi @Rafael Pinto Sperafico 

import groovy.json.JsonSlurper
String responsestring = '''
{
"id": "13336836",
"type": "page",
"status": "current",
"title": "LCMDEV-153 : one storydf",
"body": {
"storage": {
"value": "<at:declarations /><ac:structured-macro ac:name=\"section\" ac:schema-version=\"1\" ac:macro-id=\"8d7755f6-be87-4926-80d6-9561a0002bf0\"><ac:rich-text-body><p><ac:task-list>\n <ac:task>\n <ac:task-id>3</ac:task-id>\n <ac:task-status>incomplete</ac:task-status>\n <ac:task-body>Workunit sequence, frequency, and task order has been configured for all environments (CA7, Local) </ac:task-body>\n </ac:task>\n</ac:task-list>\n<ac:task-list>\n <ac:task>\n <ac:task-id>3</ac:task-id>\n <ac:task-status>incomplete</ac:task-status>\n <ac:task-body>Story met acceptance criteria</ac:task-body>\n </ac:task>\n</ac:task-list>\n<ac:task-list>\n <ac:task>\n </p></ac:rich-text-body></ac:structured-macro>",
"representation": "storage",
"_expandable": {
"content": "/rest/api/content/13336836"
}
},
"_expandable": {
"editor": "",
"view": "",
"export_view": "",
"styled_view": "",
"anonymous_export_view": ""
}
},
"extensions": {
"position": "none"
},
"_expandable": {
"container": "/rest/api/space/TEST",
"metadata": "",
"operations": "",
"children": "/rest/api/content/13336836/child",
"restrictions": "/rest/api/content/13336836/restriction/byOperation",
"history": "/rest/api/content/13336836/history",
"ancestors": "",
"version": "",
"descendants": "/rest/api/content/13336836/descendant",
"space": "/rest/api/space/TEST"
},
"_links": {
"self": "http://URL:8090/rest/api/content/13336836",
"base": "http://URL:8090",
"context": "",
"collection": "/rest/api/content",
"webui": "/display/TEST/LCMDEV-153+%3A+one+storydf",
"edit": "/pages/resumedraft.action?draftId=13336836",
"tinyui": "/x/BIHL"
}
}
'''
JsonSlurper jsonSlurper = new JsonSlurper()
def jsonobject = jsonSlurper.parseText(responsestring)
assert jsonobject instanceof Map
String val1 = jsonobject.get("id")

 

I am using the above JSON .

My aim is to retrieve "<ac:task-status>" which is in the "value" section .

Is it possible do ?

 

Regards 

Sushma

Hi @Sushma ,

You could be doing the following:

// replace double-quotes from value with single-quotes
responsestring = responsestring.replaceAll("=\"(.*)\"","='${1}'");

// parse to JSON
def jsonobject = new JsonSlurper().setType(JsonParserType.LAX).parseText(responsestring)

// get the value property
String propertyValue = jsonobject.body.storage.value;

// extract status from status>....< tag
def status = (propertyValue =~ /status>(.+)</);

println(status[0][1]);

// Output: incomplete

Kind regards,
Rafael

Like Sushma likes this

Thanks alot @Rafael Pinto Sperafico 

I will try with the above code..

Regards

Sushma

Hi @Rafael Pinto Sperafico 

I changed to 

String propertyValue = jsonobject.getAt("body").getAt("storage").getAt("value")

Thank you

Kind Regards

Sushma 

Hi @Rafael Pinto Sperafico ,

I have one query,

def propertyValue = jsonobject.getAt("body").getAt("storage").getAt("value")

def status = (propertyValue =~ /status>(.+)</);
def body = (propertyValue =~ /body>(.+)</);

for(int i=0;i<status.size();i++){
log.info(status[i][1])
}
for(int i=1;i<body.size();i++){
log.info(body[i][1])
}

the above code will print list of status and body items.

But there is no correlation between the status and body.

is there any way to get task-status and corresponding task-body in a single data structure ?

Thanks in advance

Kind regards

Sushma

Hi @Rafael Pinto Sperafico 

What I meant is based on task-id is it possible to get the task-status and corresponding task-body ?

Regards

Sushma 

Hi @Sushma ,

Perhaps you should consider converting the "XML" provided into a readable XML, so you can iterate over it getting the information you required. For that, you could either:

Please, comment on https://jira.atlassian.com/browse/CONFSERVER-24884Create and document an XSD / DTD for Confluence markup 

Kind regards,
Rafael

Hi @Rafael Pinto Sperafico 

As you suggested,I converted the xhtml content to valid xml using replaceAll().

JsonSlurper jsonSlurper = new JsonSlurper()
responsestring = responsestring.replaceAll("=\"(.*)\"","='${1}'");
def jsonobject = new JsonSlurper().setType(JsonParserType.LAX).parseText(responsestring)
LazyValueMap propertyValue = jsonobject.getAt("body").getAt("storage") as LazyValueMap
Map.Entry<String,Value>[] items = propertyValue.items()
String value = items[0].getValue()
String xml = value.replaceAll(":","")
xml = StringUtils.substringBetween(xml, "<acrich-text-body>", "</acrich-text-body>");
def slurper = new XmlSlurper().parseText(xml)
log.info(slurper)

as a result I am getting the below output :

1incompleteWorkunit sequence, frequency, and task order has been configured for all environments (CA7, Local) 2incompleteStory met acceptance criteria3incompleteBatch list selection criteria to be tested for correctness and for performance4incompleteScreen Knowledge base should be updated (if required UI Specs)5incompleteAll Impacted Regression tests passed in Master Nightly pipeline6incompleteManual unit testing should be completed for UI Look & Feel, navigation, validation and screen resolution

How to get the individual tasklist body based on the id ?

please help me ..

Kind Regards

Sushma

Hi @Sushma ,

Here is an example:

<rich-text-body>
<task-list>
<task>
<task-id>3</task-id>
<task-status>incomplete</task-status>
<task-body>Workunit sequence, frequency, and task order has been configured for all environments (CA7, Local) </task-body>
</task>
</task-list>
<task-list>
<task>
<task-id>3</task-id>
<task-status>incomplete</task-status>
<task-body>Story met acceptance criteria</task-body>
</task>
</task-list>
</rich-text-body>

You could run the following:

slurper.getAt("task-list").'*'.find {
it ->
println(it.getAt("task-id"))
println(it.getAt("task-status"))
println(it.getAt("task-body"))
println(" ---------- ")
}

Resulting on:

3
incomplete
Workunit sequence, frequency, and task order has been configured for all environments (CA7, Local)
----------
3
incomplete
Story met acceptance criteria
----------

More information on https://www.groovy-lang.org/processing-xml.html

Kind regards,
Rafael

Like Sushma likes this

Hi @Rafael Pinto Sperafico 

Thanks a bunch :)

Regards

Sushma

Hi @Rafael Pinto Sperafico 

I need one suggestion from your end,

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.PNGhere 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 @Sushma ,

Based on what you have described, perhaps would be best when creating a page in Confluence you get the response from page creation and have it stored in Jira (e.g customField, issueLink). Then, use this information to update the page created wherever changes in a Jira issue happens (it would work as a push notification of some sort to Confluence)

Perhaps, you should consider creating the page you described (e.g Page Created from Jira) in previous comment on a separate location and have Include Page Macro to include this page (Page Created from Jira) in another page where you actually need the info to be displayed and available.

Furthermore, if you update checkbox statuses, you should be pushing those changes to Jira using the Jira Issue you have passed as argument during page creation, so you know which issue the page relates to.

It has been very interesting seeing how this thread developed. For reference, please be aware that Atlassian has the Atlassian Developer Community so instead of posting developing inquires in here, you would be much better assisted by raising development inquires in there instead.

Hope the above helps.

Kind regards;
Rafael

Suggest an answer

Log in or Sign up to answer
TAGS
Community showcase
Published in Jira

Admins, notify your Jira instance of system-wide changes with the new admin announcement banner

Hi All! We’re excited to share the launch of an announcement banner that lets Jira site administrators communicate directly to their users across their  Jira Cloud instance.  ...

694 views 17 19
Read article

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