Sync Azure DevOps History into a remote text field or comment

This script captures Azure DevOps (ADO) work item history and syncs a readable summary into Jira Cloud, preserving important change context that cannot be migrated natively.

I have used Jira cloud as an example, but it can be synced from Azure DevOps → All supported issue trackers using Exalate.

Due to Jira Cloud API limitations, it is not possible to replace or rewrite Jira issue history. As a workaround, this script collects relevant ADO history events and stores them as a formatted text summary in a Jira custom field, ensuring historical context is still visible to users.


Script Overview

  • Fetches the full update history of a work item using the Azure DevOps REST API.

  • Iterates through each update and detects meaningful actions such as:

    • Work item creation

    • Priority changes

    • Field updates (description, acceptance criteria, repro steps)

    • Comments

    • Attachments

    • Parent/child link changes

  • Builds a human-readable history log including:

    • Author + Action performed +Date
  • Limits the output to the last 10 history entries to keep the summary readable.

  • Stores the result in a replica custom key.

Jira Cloud Incoming Sync

  • Reads the history summary from the replica.

  • Writes it into a Jira custom field for reference.


Final Solution

Azure DevOps Outgoing sync

def workItemId = replica.key

def response = httpClient.get("/_apis/wit/workItems/${workItemId}/updates?api-version=7.1-preview.3")

if (response?.value) {
    def updates = response.value
    def history = []

    updates.eachWithIndex { update, idx ->
        def author = update.revisedBy?.displayName ?: "Unknown"
        def rawDate = update.revisedDate
        def date = rawDate?.startsWith("9999") ? null : rawDate?.take(10)

        if (!date && idx > 0) {
            def prevDate = updates[idx - 1]?.revisedDate
            date = prevDate?.startsWith("9999") ? "latest" : prevDate?.take(10) ?: "latest"
        }
        if (!date) date = "latest"

        def action = ""

        if (update.id == 1) {
            def type = update.fields?.get("System.WorkItemType")?.newValue ?: "work item"
            action = "created the ${type}"
        }

        if (update.fields?.containsKey("Microsoft.VSTS.Common.Priority")) {
            def newValue = update.fields["Microsoft.VSTS.Common.Priority"].newValue
            action = "changed Priority to ${newValue}"
        }

        if (update.fields?.containsKey("System.Description") ||
            update.fields?.containsKey("Microsoft.VSTS.Common.AcceptanceCriteria") ||
            update.fields?.containsKey("Microsoft.VSTS.TCM.ReproSteps")) {
            action = action ? "${action} and made field changes" : "made field changes"
        }

        if (update.fields?.containsKey("System.History")) {
            action = action ? "${action} and added a comment" : "added a comment"
        }

        if (update.relations?.added?.any { it.rel == "AttachedFile" }) {
            def count = update.relations.added.count { it.rel == "AttachedFile" }
            action = "added ${count > 1 ? count + ' attachments' : 'an attachment'}"
        }

        if (update.relations?.added?.any { it.rel?.contains("Hierarchy-Forward") }) {
            def name = update.relations.added.find { it.rel.contains("Hierarchy-Forward") }?.attributes?.name ?: "Child"
            action = "added ${name} link"
        }

        if (action) {
            history << "${author} ${action} (${date})"
        }
    }

    def summaryText = history.takeRight(10).join("\n")
    replica.customKeys."ADO History Summary" = summaryText
}

Jira Cloud Incoming sync

def adoHistory = replica.customKeys."ADO History Summary"
if (adoHistory) {
    issue.customFields."ADO History".value = adoHistory
}

You can also add it into a comment

def historyComment = replica.customKeys."ADO History Summary"
issue.comments = commentHelper.addComment(historyComment, issue.comments)

Version

This script is currently working on version 5.29