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
