HowTo synchronize issue relations from ADO to Jira Cloud

Originally asked by Thorsten on 14 November 2023 (original question)


Hi,

I can’t find anything in the documentation on how to synchronize ticket relationships other than parent/child from ADO to Jira Cloud. I need to implement following relationships as soon as possible:

  • “Relates to”
  • “Is related”
  • “Blocks”
  • “Is Blocked by”
  • “Duplicates”
  • “Is Duplicated by”
  • “Tests”
  • “Is Tested by”

Can some please help me with this case?

BR,

Thorsten


Comments:

Javier Pozuelo commented on 16 November 2023

Hello Thorsten,

Let me test these relationships and I will get back to you later today.

Javier Pozuelo commented on 16 November 2023

Hello Thorsten,

I need some clarification on how you want to synchronize these relationships. Let’s say you have a work Item in ADO that is related to another one, when you Exalate it to Jira Cloud, what do you expect to happen when the ADO Work Item and the Jira Cloud ticket are synchronized?

Thorsten commented on 17 November 2023

Hi Javier,

I expect the ADO ticket’s relations to also exist in Jira, so that the same relations exist in Jira as in ADO. The relations only need to be synced from ADO to Jira and not back, which might make it easier.

Is that enough of an answer for you?

BR,

Thorsten

Javier Pozuelo commented on 17 November 2023

Hello Thorsten,

Thank you for the clarification. We are working on an implementation for this use case, I will have an update for you by Monday.

Kind Regards

Answer by Javier Pozuelo on 20 November 2023

Hello Thorsten,

  • In ADO, the “Parent-Child” relationship can be mapped to the “Epic-Story” or “Sub-task” relationships in Jira.
  • ADO doesn’t have a relationship type “Block”, but the “Predecessor-Successor” relationship in ADO is equivalent to the “Blocks” relationship in Jira.
  • Jira Cloud doesn’t have Issue link types “Testing”

You need to place the following script in theOutgoing Sync of Azure DevOps. This will run an API call that gets all the relations from the Azure DevOps workItem and adds it to the replica, so it can be sent over to Jira Cloud.

Outgoing Sync of Azure DevOps

replica.parentId = workItem.parentId

def res = httpClient.get("/_apis/wit/workitems/${workItem.key}?\$expand=relations&api-version=6.0",false)
if (res.relations != null)
replica.relations = res.relations

In the Incoming sync of Jira Cloud we need to parse the relations data being sent by ADO and make an API call to find the Issue that needs to be linked.
I have made the following relationships for you in the script below

  • ADO’s “Predecessor-Successor” relationships maps to Jira’s “Blocks” relationship
  • ADO’s “Related” relationship to Jira’s “Relates” relationship.
  • ADO’s “Duplicate” relationship to Jira’s “Duplicate” relationship

Incoming Sync of Jira Cloud

replica.relations.each {
    relation ->
    if (relation.attributes.name == "Duplicate"){
            def a = syncHelper.getLocalIssueKeyFromRemoteId(relation.url.tokenize('/')[7])//?.urn   
           // debug.error(issue.issueLinks[0].otherIssueId.toString())
            if (issue.issueLinks[0]?.otherIssueId != a.id){
                def res = httpClient.put("/rest/api/2/issue/${issue.key}", """
                {
                   "update":{
                      "issuelinks":[
                         {
                            "add":{
                               "type":{
                                  "name":"Duplicate"
                               },
                               "outwardIssue":{
                                  "key":"${a.urn}"
                               }
                            }
                         }
                      ]
                   }
                }
                """)
            }
        } else if(relation.attributes.name == "Related"){
            def a = syncHelper.getLocalIssueKeyFromRemoteId(relation.url.tokenize('/')[7])//?.urn   
            if (issue.issueLinks[0]?.otherIssueId != a.id){
                def res = httpClient.put("/rest/api/2/issue/${issue.key}", """
                {
                   "update":{
                      "issuelinks":[
                         {
                            "add":{
                               "type":{
                                  "name":"Relates"
                               },
                               "outwardIssue":{
                                  "key":"${a.urn}"
                               }
                            }
                         }
                      ]
                   }
                }
                """)
            }
        }else if(relation.attributes.name == "Predecessor"){
            def a = syncHelper.getLocalIssueKeyFromRemoteId(relation.url.tokenize('/')[7])//?.urn   
            if (issue.issueLinks[0]?.otherIssueId != a.id){
                def res = httpClient.put("/rest/api/2/issue/${issue.key}", """
                {
                   "update":{
                      "issuelinks":[
                         {
                            "add":{
                               "type":{
                                  "name":"Blocks"
                               },
                               "outwardIssue":{
                                  "key":"${a.urn}"
                               }
                            }
                         }
                      ]
                   }
                }
                """)
            }
        }
}

In case you want more information or try to make this sync bidirectional, this answer is based on the following documentation: Jira Cloud Azure DevOps: Bi-directional hierarchy sync (old community)

Kind regards


Comments:

Thorsten commented on 27 November 2023

Hi Javier Pozuelo,

sorry for the late response I haven’t been available for the last week. Thank you for your detailed answer, I have tested the whole thing and it’s working. (old community)

BR,

Thorsten

Answer by Thorsten on 26 January 2024

Hi,

I don’t know why, but the script snippet no longer works or maybe it only worked in the tests where I hadn’t synchronized dozens of issues using a bulk operation.

I’ll try to describe the problem: if I have 2 tickets that have a relation to each other, the script aborts in this line with the following error message:

if (issue.issueLinks[0]?.otherIssueId != a.id){
Cannot get property 'id' on null object 

If I change the line as follows, no error occurs but the relations are not created either:

if (issue.issueLinks[0]?.otherIssueId != a?.id){

However, if I change one of the tickets (with the “a?.id” snippet), the change and also the relation are entered.
To me it looks like we have a chicken and egg problem here, because the variable “a.id” has no content during the first sync because the ticket does not yet exist at this point.

Is there a way to generate the ticket before the relation part of the script is running?

BR,

Thorsten


Comments:

Javier Pozuelo commented on 01 February 2024

Hello Thorsten,

Could you try adding store(issue) in the firstSync block?

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.