The Exalate team will be on holiday for the coming days - returning Jan 4
Enjoy & stay safe

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
languagegroovy
titleIncoming Sync Azure Devops
linenumberstrue
collapsetrue
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(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
}


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

      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)
    }
      }       
      workItem.iterationPath = 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
        }
    }
} 



Here is a video;


Widget Connector
urlhttp://youtube.com/watch?v=GB49BeyzLIU





Thank you and happy exalating.

...