Sync Jira assignees to ZD dropdown for user selection

Originally asked by George Smith on 16 May 2023 (original question)


We need a user-defined drop-down field in ZD to be populated with data from all possible assignees in Jira i.e. if a new user in Jira Cloud is created and assigned an issue, this should create a new value in the ZD drop-down and populate the field with this value.


Answer by Syed Majid Hassan on 17 May 2023

Hi George Smith,

The way I have designed this use case is that with every sync transaction, Jira sends out a list of all users. Then on the ZD side, this payload is received and populated into a ZD drop down field. The scripts to be used are the following:

Jira Outgoing Expand source

def a = new JiraClient(httpClient).http("GET", "/rest/api/3/user/assignable/multiProjectSearch",["projectKeys":["CM"]], null, [:])
def jsonSlurper = new groovy.json.JsonSlurper()
def parseText = jsonSlurper.parseText(a)

def asg_list = new String[parseText.size()]
def asg_email = new String[parseText.size()]
for(int i=0; i<parseText.size(); i++){
    if (parseText[i].displayName != null){
        asg_list[i] = parseText[i].displayName
        asg_email[i] = parseText[i].emailAddress
    }
}  
replica.assignee_list = asg_list
replica.assignee_email = asg_email

Zendesk Incoming script Expand source

class InjectorGetter {


    static Object getInjector() {
        try {
            return play.api.Play$.MODULE$.current().injector()
        } catch (e) {
            def context = com.exalate.replication.services.processor.CreateReplicaProcessor$.MODULE$.threadLocalContext.get()
            if (!context) {
                context = com.exalate.replication.services.processor.ChangeIssueProcessor$.MODULE$.threadLocalContext.get()
            }
            if (!context) {
                context = com.exalate.replication.services.processor.CreateIssueProcessor$.MODULE$.threadLocalContext.get()
            }
            if (!context) {
                throw new com.exalate.api.exception.IssueTrackerException(""" No context for executing external script CreateIssue.groovy. Please contact Exalate Support.""".toString())
            }
            context.injector
        }
    }
}
class ZdClient {
    // 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 play.api.inject.Injector getInjector() {
        InjectorGetter.getInjector()
    }

    private static def getGeneralSettings() {
        def gsp = InjectorGetter.getInjector().instanceOf(com.exalate.api.persistence.issuetracker.IGeneralSettingsRepository.class)
        //def gsp = InjectorGetter.getInjector().instanceOf(com.exalate.api.persistence.issuetracker.IGeneralSettingsPersistence.class)
        def gsOpt = await(gsp.get())
        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

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

    ZdClient(httpClient, debug) {
        this.httpClient = httpClient
        this.debug = debug
    }
    def 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)

        def response
        try {
            def request = httpClient
                    .zendeskClient
                    .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 authorizationHeader = await(httpClient.zendeskClient.getAuthHeaderFromGs())
            request = request.addHttpHeaders(scala.collection.JavaConversions.asScalaBuffer([pair("Authorization", authorizationHeader) as scala.Tuple2<String, String>]))
            //debug.error("${request.method()} ${request.url()} ${request.headers()}")
            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
        }
    }
}


issue.labels  = replica.labels
issue.summary      = replica.summary
issue.description  = replica.description ?: "No description"
issue.attachments  = attachmentHelper.mergeAttachments(issue, replica)
issue.comments     += replica.addedComments


def mapAssignees = [[:]]
for (int i=0; i<replica.assignee_list.size(); i++){
    if(replica.assignee_email[i] != ""){
        Map temp = [:]
        temp.put("name" , "${replica.assignee_list[i]}")
        temp.put("value" , "${replica.assignee_email[i]?.replaceAll('@', '__')}")
        mapAssignees[i] = temp
      }
      else 
      {
        Map temp = [:]
        temp.put("name" , "${replica.assignee_list[i]}")
        temp.put("value" , "test${i}__email.com")
        mapAssignees[i] = temp
      }
      
}

Map<String,List<String>> queryParams = ["ticket_field": ["custom_field_options": mapAssignees]]
def queryParamsJson = groovy.json.JsonOutput.toJson(queryParams)

def res = new ZdClient(httpClient, debug)
    .http(
            "PUT",
            "/api/v2/ticket_fields/7309935056017",
            [:],
            "${queryParamsJson}",
            ["Accept": ["application/json"], "Content-type" : ["application/json"]]
    ) 
    { 
        response ->
        if (response.code >= 400) {
            throw new com.exalate.api.exception.IssueTrackerException("Failed to get orgnaizations with name ")
        } else response.body as String
    }

if (replica.assignee?.email)
issue.customFields."Jira Assignee".value.value = replica.assignee.email.replaceAll('@','__')

Here is a short video demonstrating the solution in action:

Thanks

Majid


Answer by Kazim Yildirim on 22 May 2023

Hi Majid,
I am getting an error and the list of email addresses is empty…remote replica is not showing the adresses.

How to proceed at that point?


Comments:

Syed Majid Hassan commented on 22 May 2023

What is the error please?

And which side is it on?

Kazim Yildirim commented on 22 May 2023

Failed to get organizations name…on ZD side

Kazim Yildirim commented on 22 May 2023

does this help?

Syed Majid Hassan commented on 23 May 2023

Not really, as it does not really give me much. Can you please book a slot here and we will give it another shot.

Thanks

Majid

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