Syncing Salesforce Chatter Posts to Azure DevOps Discussion Section

Originally asked by Kaleigh Garcia on 25 October 2024 (original question)


Hello Exalate Community,

I’m working on a sync configuration between Salesforce and Azure DevOps (ADO) with the goal of syncing Salesforce Chatter posts to the discussion section in ADO. I’m currently using the BasicHubComment object in my script to map Chatter comments from Salesforce to workItem.comments in ADO. However, these comments are not appearing in ADO’s discussion section as expected.

I’ve referred to Exalate’s resource for syncing Chatter posts to Jira comments, hoping to adapt this approach to work with ADO’s discussion section. Here’s a breakdown of my current configuration for both the outgoing and incoming scripts across Salesforce and ADO.

Any guidance on this would be helpful!

Salesforce Outgoing (from Exalate resource ):

replica.comments = entity.comments.inject([]) { result, comment ->
    def res = httpClient.get("/services/data/v54.0/query/?q=SELECT+Name+from+User+where+id=%27${comment.author.key}%27")
    comment.body = nodeHelper.stripHtml(res.records.Name[0] + " commented: " + comment.body)
    result += comment
 
    def feedResponse = httpClient.getResponse("/services/data/v54.0/chatter/feed-elements/${comment.idStr}")
    def js = new groovy.json.JsonSlurper()
    def feedJson = groovy.json.JsonOutput.toJson(feedResponse.body)
    feedResponse.body.capabilities.comments.page.items.collect {
        res = httpClient.get("/services/data/v54.0/query/?q=SELECT+Name+from+User+where+id=%27${it.user.id}%27")
        def c = new com.exalate.basic.domain.hubobject.v1.BasicHubComment()
        c.body = res.records.Name[0] + " commented: " + it.body.text
        c.id = it.id
        result += c
        }
    result
    }

Salesforce Incoming (from Exalate resource ):

def commentMap = [
    "mathieu.lepoutre@idalko.com" : "0058d000004df3DAAQ",
    "syed.majid.hassan@idalko.com" : "0057Q000006fOOOQA2"
]

def flag = 0

// Loop through added comments
replica.addedComments.collect { comment ->
    def matcher = comment.body =~ /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/ // Regex to find email addresses
    def newCommentBody = comment.body

    matcher.each { matchedEmail ->
        newCommentBody = newCommentBody.replace(matchedEmail[0], "") // Remove the email from the comment body

        // Post the comment with mention
        def res = httpClient.post("/services/data/v54.0/chatter/feed-elements", 
        "{\"body\":{\"messageSegments\":[{\"type\":\"Text\", \"text\":\"${newCommentBody} \" },{\"type\":\"Mention\", \"id\":\"${commentMap[matchedEmail[0]]}\"}]},\"feedElementType\":\"FeedItem\",\"subjectId\":\"${entity.Id}\"}")

        flag = 1
    }
}

// If no email mentions were found, sync the comments normally
if (flag == 0) {
    entity.comments = commentHelper.mergeComments(entity, replica) 
}

ADO Outgoing:

replica.chatterPosts.each { chatterPost ->
    def newComment = [
        "text": chatterPost.body.messageSegments.collect { it.text }.join(" ")
    ]
    
    def discussionUrl = "/_apis/wit/workItems/${workItem.id}/comments?api-version=6.0-preview.3"
    def res = httpClient.post(discussionUrl, newComment)
    
    if (res.status != 200) {
        logger.error("Failed to post comment to ADO Discussion: ${res.body}")
    }
}

ADO Incoming:

if (!workItem.comments) {
    workItem.comments = []
}

if (replica.chatterPosts) {
    replica.chatterPosts.each { chatterPost ->
        def adoComment = new com.exalate.basic.domain.hubobject.v1.BasicHubComment()
        adoComment.body = chatterPost.messageSegments.collect { it.text }.join(" ")
        adoComment.author = nodeHelper.getUserByEmail(chatterPost.author.email) ?: chatterPost.author.displayName
        adoComment.created = chatterPost.createdDate
        workItem.comments += adoComment
    }
}