This post will show you code snippets you can use to sync over comments from Jira Cloud to Azure DevOps & keep the right format.
This will keep the format for Headings, Lists, Url’s, Bold & Italic text and it will also do inline user mentions.
(Inline images will be added later).
Jira Cloud
First we need the comments to come from Jira Cloud to ADO so we start in the JC outgoing sync.
// Replace:
replica.comments = issue.comments
// To:
// This will change the user mentions from an ID to an email (needed in ADO)
replica.comments = issue.comments.collect{
c ->
def matcher = c.body =~ /\[~accountid:(.*?)]/
matcher.each {
def user = nodeHelper.getUser(matcher.group(1))?.email ?: "Stranger"
c.body = c.body.replace(matcher.group(1),user)
}
c
}
We apply changes on the comment if the coment has a user mention in it → [~accountid:], this is done with REGEX to find the right pattern /[~accountid:(.*?)]/ will find all the user ID’s.
With the help of our nodeHelper we can find the email address linked to the userID, if we found an email we replace the email with the ID.
the comment will be synced over with the right email.
Azure DevOps
On the ADO side we need to add a new class that will convert Wiki to HTML.
This class has a constructor that needs the nodeHelper and the projectName (String).
Wiki to Html Class
class lineProcessResult{
int index
String value
lineProcessResult(int index, String value) {
this.index = index
this.value = value
}
}
class WikiToHtml{
def helper;
def projectName;
public def WikiToHtml(def helper, def projectName){
this.helper = helper;
this.projectName = projectName;
}
private def processList(def lines, int index) {
def regex = /^\s*([#*]) \s*(.*)$/
def matches = lines[index] =~ regex
if(!matches.find())
return new lineProcessResult(index, "")
def i = index
def listItems = []
while(i < lines.size()) {
def match = lines[i] =~ regex
if(!match.find())
break
def tmp = match.group(2)
tmp = processBoldAndItalicText(tmp)
tmp = processUrl(tmp, true)
listItems += "<li>${tmp}</li>"
i++
}
def listType = matches.group(1) == "*" ? "ul" : "ol"
return new lineProcessResult(i, "<${listType}>${listItems.join()}</${listType}>")
}
private def processHeader(String line) {
def regex = /^\s*h([0-6])\.\s*(.*)$/
def matches = line =~ regex
if(!matches.find())
return ""
return "<h${matches.group(1)}>${matches.group(2)}</h${matches.group(1)}><br>"
}
private def processUrl(String line, Boolean isList){
// Separate handling for links to ensure they match the correct format
def regex = /\[(.*?)\s*\|\s*(.*?)\]/ /* /\[([^\[\]|]+)\|([^\[\]]+)\]/ */
def matches = line =~ regex
//Check if the pattern found a match
if (!matches.find()) {
// When no match is found we return the Original line if the isList is true otherwise we return an empty String
return isList ? line : ""
}
// We add a line break if it's not a list item, list items don't need a line break.
if(matches.group(2).startsWith("http") && isList)
return "<a href=\"${matches.group(2)}\">${matches.group(1)}</a>"
else
return "<a href=\"${matches.group(2)}\">${matches.group(1)}</a><br>"
}
// This function will keep the format if you have bold italic text and regular bold/italic text
private def processBoldAndItalicText(String line) {
def regex = /\[~accountid:([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\]/
// Process bold text
line = replaceText(line, /\*(.+?)\*/, '<strong>', '</strong>')
// Process italic text
line = replaceText(line, /_(.+?)_/, '<em>', '</em>')
def matches = line =~ regex
if(matches.find()) return "<br>"
return line
}
private def replaceText(String text, def regex, String startTag, String endTag) {
def matcher = text =~ regex
StringBuffer sb = new StringBuffer()
while (matcher.find()) {
matcher.appendReplacement(sb, "${startTag}${matcher.group(1)}${endTag}")
}
matcher.appendTail(sb)
return sb.toString()
}
private String processUserMention(String line){
def regex = /\[~accountid:([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\]/
def matches = line =~ regex
if(!matches.find())
return ""
matches.each {
def user = this.helper.getUserByEmail(matches.group(1), this.projectName)
if(!user){
line = line.replace(matches.group(0), matches.group(1))
}else{
line = line.replace(matches.group(0), "<a href=\"#\" data-vss-mention=\"version:2.0,"+user?.key+"\"></a>")
}
}
return line + "<br>"
}
public def wikiToHTML(String wiki){
def splitted = wiki.split(System.lineSeparator())
String text = ""
int index = 0
while(index < splitted.size()){
def lineResult = processList(splitted, index)
index = lineResult.index
def appender = lineResult.value
String headerResult = processHeader(splitted[index])
if(headerResult){
appender += headerResult
index++ // Increment the index to skip the header line in the next loop iteration
}
// Process URLs separately to ensure they don't get duplicated in text output
String newUrl = processUrl(splitted[index], false)
if (newUrl){
appender += newUrl
index++ // Increment the index to skip the URL line in the next loop iteration
} else {
// Only process bold and italic text if the line is not a URL
appender += processBoldAndItalicText(splitted[index])
}
String userMention = processUserMention(splitted[index])
if (userMention){
appender += userMention
}
if(appender.isEmpty())
text += splitted[index] + "<br>"
else
text += appender
index++
}
// This will set the color if there are color atributes.
text = text.replaceAll(/\{color:#([0-9a-fA-F]{6})\}(.*?)\{color\}/, "<span style=\"color:#\$1\">\$2</span>")
return text
}
}
Add this class at the top of your script.
Create a new WikiToHtml object and call the wikiToHTML method, this method expects a String.
The method will return your given String (Wiki format) and will convert it to HTML.
To apply this on your comments you can do something like this.
Azure DevOps incoming sync
WikiToHtml convert = new WikiToHtml(nodeHelper, workItem.project?.name)
workItem.comments = commentHelper.mergeComments(workItem,
replica, {c ->
c.body = convert.wikiToHTML(c.body)
c
})