Just a heads up: On March 24, 2025, starting at 4:30pm CDT / 19:30 UTC, the site will be undergoing scheduled maintenance for a few hours. During this time, the site might be unavailable for a short while. Thanks for your patience.
×Hi,
this is midway between Jira and Confluence (server).
I need to generate a summary table from a list of Jira Issues in a Confluence page.
I can't use the Issue macro in Confluence, because the people who will have access to the page won't have permissions in Jira, so they just would see an error message in the field in the table. In addition, I want people to be able to insert inline comments, so what they find in the page should be selectable text, possibly not rendered macro.
Anyway, I had almost done the job with the following code (here I'm not yet sending the data to Confluence, I'll do it then with a REST call).
I could get the data I need from a Jira Structure, or directly from a Jira query (in this case I'm reading a Structure).
//Retrieve information from an LOP stored in Jira Structure
//Name of the Structure to call
String structureName="MARTHA-LOP"
//Generic import
import com.atlassian.jira.component.ComponentAccessor
import groovy.xml.MarkupBuilder
//Librerie per gestione Link con Confluence
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
//Specific import for Jira Structure
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.almworks.jira.structure.api.structure.StructureManager
import com.almworks.jira.structure.api.permissions.PermissionLevel
import com.almworks.jira.structure.api.forest.ForestSpec
import com.almworks.jira.structure.api.row.StructureRow
import com.almworks.jira.structure.api.row.RowManager
import com.almworks.jira.structure.api.structure.Structure
import com.almworks.jira.structure.api.StructureComponents
import com.almworks.jira.structure.api.item.ItemIdentity
import com.almworks.jira.structure.api.item.CoreIdentities
import com.almworks.jira.structure.api.attribute.StructureAttributeService
import com.almworks.jira.structure.api.attribute.CoreAttributeSpecs
import com.almworks.jira.structure.api.attribute.AttributeSpec
import com.almworks.jira.structure.api.attribute.AttributeSpecBuilder
import com.almworks.jira.structure.api.attribute.ValueFormat
import com.almworks.jira.structure.api.attribute.VersionedRowValues
import com.almworks.integers.LongArray
import com.almworks.integers.LongIterator
def issueManager = ComponentAccessor.getIssueManager()
//Stabilisco un collegamento con Confluence
def ApplicationLink getPrimaryConfluenceLink() {
def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService.class)
final ApplicationLink conflLink = applicationLinkService.getPrimaryApplicationLink(ConfluenceApplicationType.class)
conflLink
}
def confluenceLink = getPrimaryConfluenceLink()
assert confluenceLink // must have a working app link set up
def authenticatedRequestFactory = confluenceLink.createImpersonatingAuthenticatedRequestFactory()
//Also necessary for Jira Structure
@WithPlugin("com.almworks.jira.structure")
@PluginModule
StructureComponents sc
//Structure handlers
StructureManager sm = sc.getStructureManager()
RowManager rowManager = sc.getRowManager()
def forestService = sc.getForestService()
//Permission access to structure
def permission = PermissionLevel.valueOf("ADMIN")
//Get Structure
def struct = sm.getStructuresByName(structureName, permission)[0]
//Forest Specification
def forestSpec = ForestSpec.structure(struct.getId())
//Forest Source
def forestsrc=forestService.getForestSource(forestSpec)
//Forest - Last version
def forest = forestSrc.getLatest().getForest()
//Forest Rows
def forestRows=forest.getRows()
//
def myStructureId=struct.getId() // get id
def viewManager = sc.getViewManager()
log.warn("Structure Id: "+myStructureId)
def views=viewManager.getViewSettings(myStructureId).getAssociatedViews()
views.each {
associatedView->
def viewId = associatedView.getViewId()
log.warn(viewId)
def viewSpec = viewManager.getView(viewId, null /*permission level*/).getSpecification()
viewSpec.getColumns().forEach({
column ->
//log.warn(column)// request values by column spec (attr id and params)
})
}
//List of attributes to be retrieved and then used somewhere
List attributeSet = new ArrayList<>()
attributeSet.add(CoreAttributeSpecs.KEY)
attributeSet.add(CoreAttributeSpecs.SUMMARY)
attributeSet.add(CoreAttributeSpecs.DESCRIPTION)
//Build a matrix that relates forest rows with the attributes chosen before
VersionedRowValues values = sc.getAttributeService().getAttributeValues(forestSpec, forestRows, attributeSet);
log.warn(values)
//Def writer and xml to build output
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
//Build table
xml.table(class: "aui") {
thead {
tr {
th("Key")
th("Summary")
th("Description (got from structure)")
th("Description (direct from jira issue)")
}
}
// Row counter
int rowNumber=0
tbody{
//Iterate among forest rows
for (LongIterator ri : forestRows) {
//Get the specific row
StructureRow row = rowManager.getRow(ri.value())
rowNumber=rowNumber+1
//Identify type of item in the row
ItemIdentity itemId = row.getItemId()
tr{
//In case of an Issue
if (CoreIdentities.isIssue(itemId)){
//definisco oggetto issue
def issue = issueManager.getIssueObject(values.get(ri.value(), CoreAttributeSpecs.KEY))
def issueKey =values.get(ri.value(), CoreAttributeSpecs.KEY)
td(values.get(ri.value(), CoreAttributeSpecs.KEY))
td(values.get(ri.value(), CoreAttributeSpecs.SUMMARY))
td(values.get(ri.value(), CoreAttributeSpecs.DESCRIPTION))
td(issue.getDescription())
}
//In case of another item "(normally folders)"
else{
}
}
}
}
}
//Evaluate output in Scriptrunner console
return writer.toString()
The problem then is that I'm not able to render the Issue Description in the same way I see it in Jira :-(
Here is how the description is rendered in Jira
and here is what I get in my table
When I send the data to confluence I get the same effect above.
Thanks in advance for any suggestment.
Andrea
Hi
You wrote
>>The problem then is that I'm not able to render the Issue Description in the same way I see it in Jira :-(
Have you tried to convert description from wiki to html format?
Here is an example from scriptrunner console, may be it will be helpful
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.event.api.EventPublisher
import com.atlassian.jira.util.velocity.VelocityRequestContextFactory
import com.atlassian.jira.issue.fields.renderer.wiki.AtlassianWikiRenderer
import com.atlassian.jira.issue.fields.renderer.wiki.*
import com.atlassian.jira.issue.fields.renderer.*
import com.atlassian.jira.config.properties.ApplicationProperties
import com.atlassian.jira.config.FeatureManager
def ap = ComponentAccessor.getApplicationProperties();
def fm = ComponentAccessor.getOSGiComponentInstanceOfType(FeatureManager);
def ism = ComponentAccessor.getIssueManager();
MutableIssue iss = ism.getIssueByCurrentKey('<issue Key>');
def desc = iss.getDescription();
EventPublisher eventPublisher = ComponentAccessor.getOSGiComponentInstanceOfType(EventPublisher.class);
VelocityRequestContextFactory velocityRequestContextFactory = ComponentAccessor.getOSGiComponentInstanceOfType(VelocityRequestContextFactory.class);
def wikiRenderer = new AtlassianWikiRenderer(eventPublisher, ap, velocityRequestContextFactory, fm);
String descHtml = wikiRenderer.render(desc, null);
return descHtml
Hy @fjodors , and thanks for your message.
The content of your descHtml is actually the same the content I got already in the strings I was using.
If I return directly this string to the console, it works perfectly.
The main issue (for me, due to my limited knowledge), is how to generate the equivalent MarkupBuilder instructions from the string, in order to put it in the table cell.
If I simply add
td('<p><b>20/11/19-A.Boerio:</b>Ciao</p>')
and then return the writer, I get a table with a cell that shows
<p><b>20/11/19-A.Boerio:</b>Ciao</p>
and not
20/11/19-A.Boerio:Ciao
I'm sorry I don't know how can I describe it better.
Andrea
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hmmm... I found similar problem in this article https://community.atlassian.com/t5/Marketplace-Apps-Integrations/MarkupBuilder-throws-an-error-in-script-REST-endpoint-in/qaq-p/748834
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Ops, I guess I could solve it by using mkp.yieldUnescaped()
I try.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Done!
It's was just a matter of introducing
def mkp = new MarkupBuilderHelper(xml)
and then, when defining the table, using
td{
mkp.yieldUnescaped('<p><b>20/11/19-A.Boerio:</b>Ciao</p>')
}
instead of
td('<p><b>20/11/19-A.Boerio:</b>Ciao</p>')
So simple ... when you know it :-(
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.