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
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
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
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.
I changed to
String propertyValue = jsonobject.getAt("body").getAt("storage").getAt("value")
Thank you
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 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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
What I meant is based on task-id is it possible to get the task-status and corresponding task-body ?
Regards
Sushma
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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-24884 - Create and document an XSD / DTD for Confluence markup
Kind regards,
Rafael
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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
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.
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)
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.
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
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.