Introduction
Hi all,
Jira processes data using Jira Wiki, while Azure DevOps utilizes HTML for data processing.
One limitation we face out-of-the-box is the inability to synchronize images between these systems.
However, with Exalate this will be possible. By utilizing the script mode, we can modify the language used by these systems to interpret and handle data.
To learn more about this solution, I added a video tutorial.
The code:
Outgoing Sync Jira On Prem Expand source
import com.atlassian.jira.component.ComponentAccessor class WikiToHtml { static String transform(String wikiFormat) { if (!wikiFormat) { return null } // access the correct services def jcl = ComponentAccessor.classLoader def app = ComponentAccessor.getApplicationProperties() def epubClass = jcl.loadClass("com.atlassian.event.api.EventPublisher") def epub = ComponentAccessor.getOSGiComponentInstanceOfType(epubClass) def fmanClass = jcl.loadClass("com.atlassian.jira.config.FeatureManager") def fman = ComponentAccessor.getOSGiComponentInstanceOfType(fmanClass) def vreqClass = jcl.loadClass("com.atlassian.jira.util.velocity.VelocityRequestContextFactory") def vreq = ComponentAccessor.getOSGiComponentInstanceOfType(vreqClass) def wrenderClass = jcl.loadClass("com.atlassian.jira.issue.fields.renderer.wiki.AtlassianWikiRenderer") def wrender = wrenderClass.newInstance(epub, app, vreq, fman) def fixImage = wikiFormat?.replaceAll(/\!(\S+)\|\S+\!/, '<!-- inline image filename=#$1# -->') fixImage = fixImage.replaceAll(/\!\^(\S+)\|\S+\!/, '<!-- inline image filename=#$1# -->') fixImage = fixImage.replaceAll(/\!\^(\S+)\!/, '<!-- inline image filename=#$1# -->') fixImage = fixImage.replaceAll(/\!(\S+)\!/, '<!-- inline image filename=#$1# -->') // wiki text can also contain files fixImage = fixImage.replaceAll(/\[(\S+)\|\^(\S+)\]/, '<!-- inline file filename=#$2# -->') fixImage = fixImage.replaceAll(/\[\^(\S+)\]/, '<!-- inline file filename=#$1# -->') return wrender.render(fixImage, null) } } replica.description = WikiToHtml.transform(issue.description) replica.labels = issue.labels replica.comments = issue.comments.collect { comment -> comment.body = WikiToHtml.transform (comment.body) comment }
Incoming Sync Azure Devops Expand source
def processInlineImages = { str -> def processUnescapedLtGtTags = { def counter = 0 while (counter < 1000) { def matcher = (str =~ /<!-- inline image filename=#(([^#]+)|(([^#]+)#([^#]+)))# -->/) if (matcher.size() < 1) { break; } def match = matcher[0] if (match.size() < 2) { break; } //log.error("replica.attachments=${replica.attachments}") def attId = replica.attachments.find { it.filename?.equals(match[1]) }?.remoteId if (!attId) { log.error("""Could not find attachment with name ${match[1]}, known names: ${replica.attachments.filename}, match: ${replica.attachments.find { it.filename?.equals(match[1]) }} """) str = str.replace(match[0], """<!-- inline processed image filename=#${match[1]}# -->""".toString()) } else { def tmpStr = str.replace(match[0], """<img src="/secure/attachment/${attId}/${attId}_${match[1]}" />""".toString()) if (tmpStr == str) { break; } str = tmpStr } counter++ } str } def processLtGtTags = { def counter = 0 while (counter < 1000) { def matcher = (str =~ /<!-- inline image filename=#(([^#]+)|(([^#]+)#([^#]+)))# -->/) if (matcher.size() < 1) { break; } def match = matcher[0] if (match.size() < 2) { break; } def attId = replica.attachments.find { it.filename?.equals(match[1]) }?.remoteId if (!attId) { log.error("""Could not find attachment with name ${match[1]}, known names: ${replica.attachments.filename}, match: ${replica.attachments.find { it.filename?.equals(match[1]) }} """) str = str.replace(match[0], """<!-- inline processed image filename=#${match[1]}# -->""".toString()) } else { def tmpStr = str.replace(match[0], """<img src="/secure/attachment/${attId}/${attId}_${match[1]}" />""".toString()) if (tmpStr == str) { break; } str = tmpStr } counter++ } str } def processNoImage = { //"<p><img // src=\"https://jira.smartodds.co.uk/images/icons/attach/noimage.png\" // imagetext=\"Screenshot from 2022-11-18 11-09-25.png|thumbnail\" // align=\"absmiddle\" // border=\"0\" /></p>" def counter = 0 while (counter < 1000) { def matcher = (str =~ /<img src="[^"]+" imagetext="(([^"]+)\|thumbnail)" align="absmiddle" border="0" \/>/) if (matcher.size() < 1) { break; } def match = matcher[0] if (match.size() < 2) { break; } def filename = match[2] def attId = replica.attachments.find { it.filename?.equals(filename) }?.remoteId if (!attId) { log.error("""Could not find attachment with name ${filename}, known names: ${replica.attachments.filename}, match: ${replica.attachments.find { it.filename?.equals(filename) }} """) str = str.replace(match[0], """<img src="/images/icons/attach/noimage.png" processed imagetext="$filename|thumbnail" align="absmiddle" border="0" />""".toString()) } else { def tmpStr = str.replace(match[0], """<img src="/secure/attachment/${attId}/${attId}_${filename}" />""".toString()) if (tmpStr == str) { break; } str = tmpStr } counter++ } str } def processImgTagsWithIds = { //"<p>TEST DECS23456 </p> \n //<p><span class=\"image-wrap\" style=\"\"><img src=\"/rest/api/3/attachment/content/36820\"></span></p> \n //<p>TESt </p> \n //<p><span class=\"image-wrap\" style=\"\"><img src=\"/rest/api/3/attachment/content/36821\"></span></p> \n //<p>and more</p>" def counter = 0 while (counter < 1000) { def matcher = (str =~ /<img src="\/rest\/api\/3\/attachment\/content\/(\d+)">/) if (matcher.size() < 1) { return str } def match = matcher[0] //println("match[1]=$match[1]") if (match.size() < 2) { // match[0]=<img src="/rest/api/3/attachment/content/36820"> match[1]=36820 return str } def attId = match[1] def attachment = replica.attachments.find { (it.remoteId as String) == ( attId as String ) } if (!attachment) { log.error("""Could not find attachment with id ${attId}, known ids: ${replica.attachments.remoteId}, match: ${replica.attachments.find { (it.remoteId as String) == ( attId as String ) }} """) str = str.replace(match[0], """<img src="/rest/api/3/attachment/content/${attId}" processed />""".toString()) } else { def tmpStr = str.replace(match[0], """<img src="/secure/attachment/${attId}/${attId}_${attachment.filename}" />""".toString()) if (tmpStr == str) { break; } str = tmpStr } counter++ } str } //log.error("#processimages 0 $str") str = processUnescapedLtGtTags() //log.error("#processimages 1 $str") str = processLtGtTags() //log.error("#processimages 2 $str") str = processNoImage() //log.error("#processimages 3 $str") str = processImgTagsWithIds() log.error("#processimages $str") str } workItem.comments = commentHelper.mergeComments(workItem, replica, { comment -> def attrAuthor = comment.author?.displayName ?: "Default-" comment.body = "<b> ${attrAuthor} said:</b> " + comment.body comment.body = processInlineImages (comment.body) comment }) workItem.description = processInlineImages(replica.description)
Thank you.
Kind regards,
Mathieu Lepoutre
Questions
Recent Questions
- 0votes
Mention FullName from Jira On-Premise to Jira Cloud comment sync
- 0 answers
- Anthony Alberto
- Jun 03, 2024
- Space: Exalate
- 0votes
- 0votes
- 0votes
Issues are not syncing to local destination desk
- 1 answer
- John Lombardo
- Mar 21, 2024
- Space: Exalate
- 0votes
- 0votes
- 0votes
- 0votes
How to have keys match between jira cloud instances
- 2 answers
- Chris Matthews
- Feb 07, 2024
- Space: Exalate
- 0votes
how decrease dimension of /var/atlassian/application-data/jira/data/exalate
- 1 answer
- Francesco Doricchi
- Dec 14, 2023
- Space: Exalate
- 1vote
How to Impersonate Attachments in Jira Cloud?
- 0 answers
- Valeriia Solianikova
- Dec 12, 2023
- Space: Exalate