1
0
-1

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

  1. Javier Pozuelo

    Hello Thorsten,


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

  2. Javier Pozuelo

    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?

  3. Thorsten

    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

  4. Javier Pozuelo

    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

CommentAdd your comment...

2 answers

  1.  
    2
    1
    0

    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 the Outgoing 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


    Kind regards

    1. Thorsten

      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. slightly smiling face 


      BR,

      Thorsten

    CommentAdd your comment...
  2.  
    1
    0
    -1

    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

    1. Javier Pozuelo

      Hello Thorsten,


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

    CommentAdd your comment...