1
0
-1

The CreatedBy field in ADO workitem is always set to proxy user. Is there a way to change that at update time, or at issue create time when syncing a ticket from Jira Cloud.

    CommentAdd your comment...

    2 answers

    1.  
      2
      1
      0

      The Azure DevOps API only allows the CreatedBy field to be set at issue create time i.e. revision 1. 
      Hence, the only solution to this problem can be achieved via the firstSync block. 

      Here is the code in full:


      class GroovyHttpClient {
          // SCALA HELPERS
          public 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 httpClient
          private nodeHelper
          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\n$e", e)
                  }
                  parsedUri
              }
          }
          GroovyHttpClient(nodeHelper) {
              this.nodeHelper = nodeHelper
          }
          String http(String method, String url, String body, java.util.Map<String, List<String>> headers) {
              http(method, url, body, headers) { Response response ->
                  if (response.code >= 300) {
                      throw new com.exalate.api.exception.IssueTrackerException("Failed to perform the request $method $url (status ${response.code}), and body was: \n```$body```\nPlease contact Exalate Support: ".toString() + response.body)
                  }
                  response.body as String
              }
          }
          public <R> R http(String method, String _url, String body, java.util.Map<String, List<String>> headers, Closure<R> transformResponseFn) {
              def unsanitizedUrl = _url
              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.collectEntries { k, v -> [k, [v]] } as java.util.Map<String, List<String>>)
                          m
                      })()
                      : ([:] 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)
              def response
              try {
                  def request = nodeHelper.azureClient.ws
                  //.ws()
                          .url(sanitizedUrl)
                          .withMethod(method)
                          .withRequestFilter(nodeHelper.azureClient.issueTrackerClientLoggerFilterFactory.create(scala.Option.apply("#http"), com.exalate.services.utils.logging.config.WsLoggingConfig.apply(true, true, true, new scala.runtime.AbstractFunction2<String, String, String>(){
                          public def String apply(String k, String v) {
                              return v
                          }
                          })))
                      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.DefaultBodyWritables$.MODULE$.writeableOf_String
                      request = request.withBody(body, writable)
                  }
                  if (!allQueryParams?.isEmpty()) {
                      def scalaQueryParams = scala.collection.JavaConversions.asScalaBuffer(allQueryParams?.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)
                  }
                  response = await(
                          request.execute()
                  )
              } catch (Exception e) {
                  throw new com.exalate.api.exception.IssueTrackerException("Unable to perform the request $method $_url with body: \n```$body```\n, 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
              }
          }
      }
      if (firstSync){
         def res = new GroovyHttpClient(httpClient)
          .http(
                  "POST",
                  "https://dev.azure.com/exalatedemo/Majids%20Development/_apis/wit/workitems/\$Task?api-version=6.0",
                  "[{\"op\": \"add\", \"path\": \"/fields/System.Title\", \"value\": \"Sampletasktwo\"}, {\"op\": \"add\", \"path\": \"/fields/System.CreatedBy\", \"value\": \"Syed Majid Hassan\"}]",
                  ["Accept": ["application/json"], "Content-type" : ["application/json-patch+json"], "Authorization":["Basic cm9tYW4ubWVsbnljaGVua29AaWRhbGtvLmNvbTp0NXRqcjdvNWVidXpkaGNmeTQ1NWFzcjdhcHhhc3ZzZm8zZmZud2V6YWNtNTRkMmV6amFx"]]
          )
          {
              response ->
              if (response.code >= 400) 
                  throw new com.exalate.api.exception.IssueTrackerException("Failed to get orgnaizations with name ")
              else 
                  response.body as String
            
          }
           
          def js = new groovy.json.JsonSlurper()
          def json = js.parseText(res)
          //debug.error("${json.id}")
      
      workItem.id = json.id
      issue.customKeys.somethingIsChanged = true
      return new scala.Tuple2(issueKey, scala.collection.JavaConverters.asScalaBuffer(traces))
      }


      Some explanation of the code:

      • the main driver is the GroovyHttpClient that allows us to make a custom call to the Azure API to create the issue. 
      • the issue is created using the following payload:

        def res = new GroovyHttpClient(httpClient)
        .http(
              "POST",
              "https://dev.azure.com/exalatedemo/Majids%20Development/_apis/wit/workitems/\$Task?api-version=6.0",
              "[{\"op\": \"add\", \"path\": \"/fields/System.Title\", \"value\": \"Sampletasktwo\"}, {\"op\": \"add\", \"path\": \"/fields/System.CreatedBy\", \"value\": \"Syed Majid Hassan\"}]",
              ["Accept": ["application/json"], "Content-type" : ["application/json-patch+json"], "Authorization":["Basic <<your_access_token"]]
        )
        {
        response ->
        if (response.code >= 400)
        throw new com.exalate.api.exception.IssueTrackerException("Failed to get orgnaizations with name ")
        else
        response.body as String

        }
        This will set the CreatedBy field to Syed Majid Hassan in this example. Also, do not forget to plug in your API key. 

      • the last step is to link this newly created issue via Exalate to the source issue:

        def js = new groovy.json.JsonSlurper()
        def json = js.parseText(res)
        workItem.id = json.id

        issue.customKeys.somethingIsChanged = true
        return new scala.Tuple2(issueKey, scala.collection.JavaConverters.asScalaBuffer(traces))


      Please let me know if you have any questions. 


      Thanks

      Majid


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

        Thanks for the answer.


        Unfortunately, it is not possible to change the “creator” for already synched items (that is why we asked). New items are created in DevOps by us, therefore the creator is correct and we don’t need to check the solution.

         

        But if the solution would have been there when we started, we would have used it.

          CommentAdd your comment...