Sync Jira Sub-tasks to Azure DevOps as Parent–Child Work Items

This script allows you to preserve the Jira parent/sub-task hierarchy when synchronizing issues from Jira to Azure DevOps.

For example:

Jira                         Azure DevOps

PROJ-100 Task       →        User Story 123
   └─ PROJ-101      →           └─ Task 124
      Sub-task                     Child

The Jira sub-task’s parent is sent through the replica, and Exalate resolves the corresponding synchronized Azure DevOps work item before assigning it as the parent.


Script Overview

On the Jira side, we include the issue’s parentId in the replica:

replica.parentId = issue.parentId

On the Azure DevOps side, we use:

syncHelper.getLocalIssueKeyFromRemoteId()

to find the Azure DevOps work item that corresponds to the Jira parent.


Final Solution

Jira Outgoing Sync

Add the following to the Jira outgoing sync:

replica.parentId = issue.parentId

For regular Jira issues without a parent, parentId will simply be empty.

Azure DevOps Incoming Sync

On the Azure DevOps side, resolve the Jira parent to its corresponding local work item:

//
if (replica.parentId) {

    def localParent = syncHelper.getLocalIssueKeyFromRemoteId(
      replica.parentId.toLong()
    )

    if (localParent) {
        workItem.parentId = localParent.id
    }
}

A complete example could look like:

if (firstSync) {

    def typeMap = [
        "Task"    : "User Story",
        "Sub-task": "Task"
    ]

    workItem.typeName =
        nodeHelper.getIssueType(typeMap[replica.type?.name])?.name ?: "Task"

    workItem.summary = replica.summary
}

workItem.description = replica.description


// Jira Parent -> Azure DevOps Parent
if (replica.parentId) {

    def localParent = syncHelper.getLocalIssueKeyFromRemoteId(
        replica.parentId.toLong()
    )

    if (localParent) {
        workItem.parentId = localParent.id
    }
}

Notes

  • The Jira parent issue should already be synchronized with Azure DevOps before the sub-task is synchronized.

Version

5.37.0