SWISS RE - POC consultancy

jira op -> ADO: create new Sprint
Jira Outgoing

replica.customFields.Sprint = issue.customFields."Sprint"



ADO incoming

if(firstSync){
   // Set type name from source entity, if not found set a default
   workItem.projectKey  =  "Mathieu"
   workItem.typeName = nodeHelper.getIssueType(replica.type?.name)?.name ?: "Task";
}

if(workItem.typeName == "Task"){
workItem.summary      = replica.summary
workItem.description  = replica.description
workItem.attachments  = attachmentHelper.mergeAttachments(workItem, replica)
workItem.comments     = commentHelper.mergeComments(workItem, replica)
workItem.labels       = replica.labels
workItem.priority     = replica.priority
}

if(firstSync){
   // Set type name from source entity, if not found set a default
   workItem.projectKey  =  "Mathieu"
      def typeMap = [
       "Epic" : "Epic",
       "Story" : "User Story"
       ]
    workItem.typeName = nodeHelper.getIssueType(typeMap[replica.type?.name],workItem.projectKey)?.name ?: "Task"
    workItem.summary      = replica.summary
    store(issue)
}

def getCurrentSprint = { -> replica."Sprint".find {!it.state.equalsIgnoreCase("CLOSED")} }

if (replica.customFields."Sprint"?.value != null && !replica.customFields."Sprint"?.value?.empty && getCurrentSprint() != null) {
  def project = connection.trackerSettings.fieldValues."project"   
  def area = workItem.areaPath ?: workItem.project?.key ?: project
  def sprint = getCurrentSprint()
  def iteration = sprint.name
  def iterationPath = area + "\\" + iteration
  if (iterationPath != workItem.iterationPath) {






      def adoClient = new AdoClient(httpClient, nodeHelper, debug)
      def encode = {
    str ->
    if (!str) str
    else
     java.net.URLEncoder.encode(str, java.nio.charset.StandardCharsets.UTF_8.toString())
      }
        def projectName = workItem.project?.key ?: workItem.projectKey
//curl -v --user PAT:PAT  'https://dev.azure.com/ADO_Org_Name/ADO_Project_Name/_apis/wit/classificationnodes/Iterations/94e26e6c-a8f8-41e0-acd8-4e4fcadb8e6f?api-version=5.0'
// curl -v --user PAT:PAT 'https://dev.azure.com/ADO_Org_Name/ADO_Project_Name/_apis/work/teamsettings/iterations?api-version=7.1-preview.1'



      def existingIterations = adoClient
  .http (
      "GET",
      "/${encode(projectName)}/_apis/wit/classificationnodes/Iterations".toString(),
      ["api-version":["5.0"], "\$depth":["1"]],
      null,
      ["Accept":["application/json"]]
    ) { res ->
      if(res.code >= 400) debug.error("Failed to GET /${encode(projectName)}/_apis/work/teamsettings/iterations?api-version=7.1-preview.1 RESPONSE: ${res.code} ${res.body}")
      else (new groovy.json.JsonSlurper()).parseText(res.body)
    }
  ?.children
      //httpClient.get("/${project}/_apis/wit/classificationnodes/Iterations?\$depth=1&api-version=5.0", false)?.children

      if (!existingIterations.name.any {it.equalsIgnoreCase(sprint.name)}) {
          //if we need to create iterations
          def await = { f -> scala.concurrent.Await$.MODULE$.result(f, scala.concurrent.duration.Duration.apply(1, java.util.concurrent.TimeUnit.MINUTES)) }
          def creds = await(httpClient.azureClient.getCredentials())
          def token = creds.accessToken()
          def baseUrl = creds.issueTrackerUrl()
           
          def dateFormat = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
          def createIterationBody = [name:sprint.name]
          def attributes = null
          if(sprint.startDate) {
              def sd = dateFormat.format(sprint.startDate)
              attributes = ["startDate":sd]
          }
          if(sprint.endDate) {
              def ed = dateFormat.format(sprint.endDate)
              attributes = attributes ?: [:]
              attributes."finishDate" = ed
          }
          if(attributes != null) {
              createIterationBody."attributes" = attributes
          }
                   def remoteIterationName = sprint.name
          def iterationId = adoClient    
      .http (
        "POST",
        "/${encode(projectName)}/_apis/wit/classificationnodes/Iterations".toString(),
        ["api-version":["5.0"]],
        groovy.json.JsonOutput.toJson(["name": remoteIterationName]),
        ["Accept":["application/json"], "Content-Type":["application/json"]]
      ) { res ->
      if(res.code >= 400) debug.error("POST ${encode(projectName)}/_apis/wit/classificationnodes/Iterations?api-version=5.0 failed: ${res.code} ${res.body}")
      else (new groovy.json.JsonSlurper()).parseText(res.body)
    }?."identifier"
    //and associate it with the team
    adoClient
      .http (
        "POST",
        "/${encode(projectName)}/_apis/work/teamsettings/iterations".toString(),
        ["api-version":["7.1-preview.1"]],
        groovy.json.JsonOutput.toJson(["id": iterationId]),
        ["Accept":["application/json"], "Content-Type":["application/json"]]
      ) { res ->
      if(res.code >= 400) debug.error("POST ${encode(projectName)}/_apis/work/teamsettings/iterations failed: ${res.code} ${res.body}")
      else (new groovy.json.JsonSlurper()).parseText(res.body)
    }
   // debug.error("this is the iteration id ${iterationId} and this is the sprint name: ${sprint.name}")
          //httpClient.post("/${project}/_apis/wit/classificationnodes/Iterations?api-version=5.0".toString(), createIterationBodyStr)

      }
       
      workItem.iterationPath = iterationPath
  }
  //debug.error("it=${workItem.iterationPath}")
}

class AdoClient {
    // SCALA HELPERS
    private static <T> T await(scala.concurrent.Future<T> f) {
        scala.concurrent.Await$.MODULE$.result(f, scala.concurrent.duration.Duration$.MODULE$.Inf())
    }
    private static <T> T orNull(scala.Option<T> opt) { opt.isDefined() ? opt.get() : null }
    private static <T> scala.Option<T> none() { scala.Option$.MODULE$.<T> empty() }
    @SuppressWarnings("GroovyUnusedDeclaration")
    private static <T> scala.Option<T> none(Class<T> evidence) { scala.Option$.MODULE$.<T> empty() }
    private static <L, R> scala.Tuple2<L, R> pair(L l, R r) { scala.Tuple2$.MODULE$.<L, R> apply(l, r) }
    // SERVICES AND EXALATE API
    private static def getGeneralSettings() {
        def gsOptFuture = nodeHelper.azureClient.generalSettingsService.get()
        def gsOpt = await(gsOptFuture)
        def gs = orNull(gsOpt)
        gs
    }
    private static String getIssueTrackerUrl() {
        final def gs = getGeneralSettings()
        def removeTailingSlash = { String str -> str.trim().replace("/+\$", "") }
        final def issueTrackerUrl = removeTailingSlash(gs.issueTrackerUrl)
        issueTrackerUrl
    }
    private httpClient
    private static nodeHelper
    private debug
    def parseQueryString = { String string ->
        string.split('&').collectEntries { param ->
            param.split('=', 2).collect { URLDecoder.decode(it, 'UTF-8') }
        }
    }
    //Usage examples: https://gist.github.com/treyturner/4c0f609677cbab7cef9f
    def parseUri
    {
        parseUri = { String uri ->
            def parsedUri
            try {
                parsedUri = new URI(uri)
                if (parsedUri.scheme == 'mailto') {
                    def schemeSpecificPartList = parsedUri.schemeSpecificPart.split('\\?', 2)
                    def tempMailMap = parseQueryString(schemeSpecificPartList[1])
                    parsedUri.metaClass.mailMap = [
                            recipient: schemeSpecificPartList[0],
                            cc       : tempMailMap.find { it.key.toLowerCase() == 'cc' }.value,
                            bcc      : tempMailMap.find { it.key.toLowerCase() == 'bcc' }.value,
                            subject  : tempMailMap.find { it.key.toLowerCase() == 'subject' }.value,
                            body     : tempMailMap.find { it.key.toLowerCase() == 'body' }.value
                    ]
                }
                if (parsedUri.fragment?.contains('?')) { // handle both fragment and query string
                    parsedUri.metaClass.rawQuery = parsedUri.rawFragment.split('\\?')[1]
                    parsedUri.metaClass.query = parsedUri.fragment.split('\\?')[1]
                    parsedUri.metaClass.rawFragment = parsedUri.rawFragment.split('\\?')[0]
                    parsedUri.metaClass.fragment = parsedUri.fragment.split('\\?')[0]
                }
                if (parsedUri.rawQuery) {
                    parsedUri.metaClass.queryMap = parseQueryString(parsedUri.rawQuery)
                } else {
                    parsedUri.metaClass.queryMap = null
                }
                if (parsedUri.queryMap) {
                    parsedUri.queryMap.keySet().each { key ->
                        def value = parsedUri.queryMap[key]
                        if (value.startsWith('http') || value.startsWith('/')) {
                            parsedUri.queryMap[key] = parseUri(value)
                        }
                    }
                }
            } catch (e) {
                throw new com.exalate.api.exception.IssueTrackerException("Parsing of URI failed: $uri $e ", e)
            }
            parsedUri
        }
    }
    AdoClient(httpClient, nodeHelper, debug) {
        this.httpClient = httpClient
        this.nodeHelper = nodeHelper
        this.debug = debug
    }
    String http(String method, String path, java.util.Map<String, List<String>> queryParams, String body, java.util.Map<String, List<String>> headers) {
        http(method, path, queryParams, body, headers) { Response response ->
            if (response.code >= 300) {
                throw new com.exalate.api.exception.IssueTrackerException(
                        """Failed to perform the request $method $path (status ${response.code}),
and body was: ```$body```
Please contact Exalate Support: """.toString() + response.body
                )
            }
            response.body as String
        }
    }
    public <R> R http(String method, String path, java.util.Map<String, List<String>> queryParams, String body, java.util.Map<String, List<String>> headers, Closure<R> transformResponseFn) {
        def gs = getGeneralSettings()
        def unsanitizedUrl = issueTrackerUrl + path
        def parsedUri = parseUri(unsanitizedUrl)
        def embeddedQueryParams = parsedUri.queryMap
        def allQueryParams = embeddedQueryParams instanceof java.util.Map ?
                ({
                    def m = [:] as java.util.Map<String, List<String>>;
                    m.putAll(embeddedQueryParams as java.util.Map<String, List<String>>)
                    m.putAll(queryParams)
                })()
                : (queryParams ?: [:] as java.util.Map<String, List<String>>)
        def urlWithoutQueryParams = { String url ->
            URI uri = new URI(url)
            new URI(uri.getScheme(),
                    uri.getUserInfo(), uri.getHost(), uri.getPort(),
                    uri.getPath(),
                    null, // Ignore the query part of the input url
                    uri.getFragment()).toString()
        }
        def sanitizedUrl = urlWithoutQueryParams(unsanitizedUrl)
        //debug.error("#debug ${sanitizedUrl}")
        def response
        try {
            def request = ({ 
                try { httpClient.azureClient } 
                catch (e) { httpClient.issueTrackerClient }  
              })()
              .ws
              .url(sanitizedUrl)
              .withMethod(method)
            if (!allQueryParams.isEmpty()) {
                def scalaQueryParams = scala.collection.JavaConversions.asScalaBuffer(
                        queryParams
                                .entrySet()
                                .inject([] as List<scala.Tuple2<String, String>>) { List<scala.Tuple2<String, String>> result, kv ->
                                    kv.value.each { v -> result.add(pair(kv.key, v) as scala.Tuple2<String, String>) }
                                    result
                                }
                )
                request = request.withQueryString(scalaQueryParams)
            }
            if (headers != null && !headers.isEmpty()) {
                def scalaHeaders = scala.collection.JavaConversions.asScalaBuffer(
                        headers
                                .entrySet()
                                .inject([] as List<scala.Tuple2<String, String>>) { List<scala.Tuple2<String, String>> result, kv ->
                                    kv.value.each { v -> result.add(pair(kv.key, v) as scala.Tuple2<String, String>) }
                                    result
                                }
                )
                request = request.withHeaders(scalaHeaders)
            }
            if (body != null) {
                def writable = play.api.libs.ws.WSBodyWritables$.MODULE$.writeableOf_String()
                request = request.withBody(body, writable)
            }
            def credentials = await(httpClient.azureClient.credentials)
            def token = credentials.accessToken
            //debug.error("${play.api.libs.ws.WSAuthScheme$.class.code}")
            request = request.withAuth(token, token, play.api.libs.ws.WSAuthScheme$BASIC$.MODULE$)
            response = await(request.execute())
        } catch (Exception e) {
            throw new com.exalate.api.exception.IssueTrackerException(
                    """Unable to perform the request $method $path with body:```$body```,
please contact Exalate Support: """.toString() + e.message,
                    e
            )
        }
        java.util.Map<String, List<String>> javaMap = [:]
        for (scala.Tuple2<String, scala.collection.Seq<String>> headerTuple : scala.collection.JavaConverters.bufferAsJavaListConverter(response.allHeaders().toBuffer()).asJava()) {
            def javaList = []
            javaList.addAll(scala.collection.JavaConverters.bufferAsJavaListConverter(headerTuple._2().toBuffer()).asJava())
            javaMap[headerTuple._1()] = javaList
        }
        def javaResponse = new Response(response.body(), new Integer(response.status()), javaMap)
        return transformResponseFn(javaResponse)
    }
    public static class Response {
        final String body
        final Integer code
        final java.util.Map<String, List<String>> headers
        Response(String body, Integer code, java.util.Map<String, List<String>> headers) {
            this.body = body
            this.code = code
            this.headers = headers
        }
    }
} 
status mapping ADO incoming
def statusMap = [
    "Open"                           : "Open",
    "Withdrawn"                      : "Withdrawn",
    "Postponed"                      : "Postponed",
    "Closed"                         : "Closed",
    "In Development"                  : "InDevelopment",
    "Failed"                         : "Failed",
    "Accepted"                       : "Accepted",
    "In Qa"                           : "InQa",
    "Ready To StoryPoint"              : "Ready To StoryPoint",
    "Ready For Sprint"                 : "Ready For Sprint",
    "Solution Design"                 : "SolutionDesign",
    "Blocked"                        : "Blocked",
    "In Requirement Clarification"     : "In Requirement Clarification",
    "In Code Review"                   : "In Code Review"
]

println(statusMap)
epic - story linkage
outgoing sync Jira

replica.parentIdEpic = 10620 //hardcode the epic id as test
replica.parentIdEpicCF = issue.customFields."Epic Link"?.value?.id





incoming sync ado
//change project key
//change api project
//change incoming variable for epic id


if(firstSync){
   // Set type name from source entity, if not found set a default
   workItem.projectKey  =  "Mathieu"
   workItem.typeName = nodeHelper.getIssueType(replica.type?.name)?.name ?: "Task";
}

if(workItem.typeName == "Task"){
workItem.summary      = replica.summary
workItem.description  = replica.description
workItem.attachments  = attachmentHelper.mergeAttachments(workItem, replica)
workItem.comments     = commentHelper.mergeComments(workItem, replica)
workItem.labels       = replica.labels
workItem.priority     = replica.priority
}

if(firstSync){
   // Set type name from source entity, if not found set a default
   workItem.projectKey  =  "Mathieu"
      def typeMap = [
       "Epic" : "Epic",
       "Story" : "User Story"
       ]
    workItem.typeName = nodeHelper.getIssueType(typeMap[replica.type?.name],workItem.projectKey)?.name ?: "Task"
    workItem.summary      = replica.summary
    store(issue)
}

if (replica.parentIdEpic) {
   def localParent = syncHelper.getLocalIssueKeyFromRemoteId(replica.parentIdEpic.toLong())
   if (localParent) {
      workItem.parentId = localParent.id
   }
}

def res =httpClient.get("/Mathieu/_apis/wit/workItems/${workItem.id}/revisions",true)
def await = { f -> scala.concurrent.Await$.MODULE$.result(f, scala.concurrent.duration.Duration.apply(1, java.util.concurrent.TimeUnit.MINUTES)) }
def creds = await(httpClient.azureClient.getCredentials())
def token = creds.accessToken()
def baseUrl = creds.issueTrackerUrl()
def project = workItem.projectKey
def localUrl = baseUrl + '/_apis/wit/workItems/' + workItem.id
int x =0
res.value.relations.each{
    revision ->
        def createIterationBody1 = [
            [
                op: "test",
                path: "/rev",
                value: (int) res.value.size()
            ],
            [
                op:"remove",
                path:"/relations/${++x}"
            ]
        ]
 
}
 
def linkTypeMapping = [
    //System.LinkTypes.Related
    "relates to": "System.LinkTypes.Related"

]
def linkedIssues = replica.linkedIssues
if (linkedIssues) {
    replica.linkedIssues.each{
       def localParent = syncHelper.getLocalIssueKeyFromRemoteId(it.otherIssueId.toLong())
       if (!localParent?.id) { return; }
       localUrl = baseUrl + '/_apis/wit/workItems/' + localParent.id
     def createIterationBody = [
            [
                op: "test",
                path: "/rev",
                value: (int) res.value.size()
            ],
            [
                op:"add",
                path:"/relations/-",
                value: [
                    rel:linkTypeMapping[it.linkName],
                    url:localUrl,
                    attributes: [
                        comment:""
                    ]
                ]
            ]
        ]
 
def createIterationBodyStr = groovy.json.JsonOutput.toJson(createIterationBody)
        converter = scala.collection.JavaConverters;
        arrForScala = [new scala.Tuple2("Content-Type","application/json-patch+json")]
        scalaSeq = converter.asScalaIteratorConverter(arrForScala.iterator()).asScala().toSeq();
        createIterationBodyStr = groovy.json.JsonOutput.toJson(createIterationBody)
        def result = await(httpClient.azureClient.ws
            .url(baseUrl+"/${project}/_apis/wit/workitems/${workItem.id}?api-version=6.0")
            .addHttpHeaders(scalaSeq)
            .withAuth(token, token, play.api.libs.ws.WSAuthScheme$BASIC$.MODULE$)
            .withBody(play.api.libs.json.Json.parse(createIterationBodyStr), play.api.libs.ws.JsonBodyWritables$.MODULE$.writeableOf_JsValue)
            .withMethod("PATCH")
            .execute())
    
    }
}