Controlling GH issue project attributes from Jira

When you Exalate a Jira ticket to GitHub, it would not be assigned to any GitHub project (by default). In this use case, we will control the allocation of the GitHub issue to a project board, but we will base this selection from what the user selects on the Jira ticket i.e. user will select the target GitHub project for the issue from his own ticket.

Please find the code for the GitHub Incoming Sync

    import org.apache.commons.lang3.StringEscapeUtils 
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.githubClient.ws
            //.ws()
                    .url(sanitizedUrl)
                    .withMethod(method)
            if (headers != null && !headers?.isEmpty()) {
                def scalaHeaders = scala.collection.JavaConverters.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.toSeq())
            }
            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.JavaConverters.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.toSeq())
            }
         //  throw new Exception("REQUEST ${request.method()}, ${request.url()}, ${request.headers()}, ${request.body()}")
            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){
    issue.repository   = "majid-org/demo-repo"
}
issue.summary      = replica.summary
issue.description  = replica.description
issue.comments     = commentHelper.mergeComments(issue, replica)

if(!firstSync){
    //ef tok = GroovyHttpClient.await(nodeHelper.githubClient.getToken())
    //debug.error("${tok}")
    def tok = ""
    def js = new groovy.json.JsonSlurper()
    def projectJira = "demo-project"
    //replica.customFields."GitHub Project"?.value?.value
   def statusJira = "Todo"
   //replica.customFields."GitHub Status"?.value?.value
    
    def projectId = ""
    def itemId = ""
    def fieldId = ""
    def statusId = ""

    def res = new GroovyHttpClient(nodeHelper)
        .http(
            "GET",
            "https://api.GitHub.com/repos/majid-org/demo-repo/issues/${issue.key}",
            null,
            ["Content-type" : ["application/json"], "Authorization":["Bearer ${tok}".toString()]]
    ) 
    { 
        response ->
        if (response.code >= 400)
            throw new com.exalate.api.exception.IssueTrackerException("Failed")
        else
            response.body as String
    }
    def json = js.parseText(res)
    def contentId = json.node_id

    res = new GroovyHttpClient(nodeHelper)
        .http(
            "POST",
            "https://api.github.com/graphql",
            "{\"query\":\" {organization(login:\\\"majid-org\\\") {projectsV2(first: 20) {nodes {id title}}}}\"}",
            ["Content-type" : ["application/json"], "Authorization":["Bearer ${tok}".toString()]]
    )
    
   // debug.error("${res}")
    { 
        response ->
        if (response.code >= 400)
            throw new com.exalate.api.exception.IssueTrackerException("Failed")
        else
            response.body as String
    }
    json = js.parseText(res)
    json.data.organization.projectsV2.nodes.each {
    if (it?.title == "@${projectJira}")
        projectId = it.id
    }

    res = new GroovyHttpClient(nodeHelper)
        .http(
            "POST",
            "https://api.github.com/graphql",
            "{\"query\":\"mutation {addProjectV2ItemById(input: {projectId: \\\"${projectId}\\\" contentId: \\\"${contentId}\\\"}){item {id}}}\"}",
            ["Content-type" : ["application/json"], "Authorization":["Bearer ${tok}".toString()]]
    ) 
    { 
        response ->
        if (response.code >= 400)
            throw new com.exalate.api.exception.IssueTrackerException("Failed")
        else
            response.body as String
    }
    json = js.parseText(res)
    itemId = json.data.addProjectV2ItemById?.item?.id



    res = new GroovyHttpClient(nodeHelper)
        .http(
            "POST",
            "https://api.github.com/graphql",
            "{\"query\":\" { node(id: \\\"${projectId}\\\") { ... on ProjectV2 { fields(first: 20) { nodes { ... on ProjectV2Field { id name } ... on ProjectV2IterationField { id name configuration { iterations { startDate id }}} ... on ProjectV2SingleSelectField { id name options { id name }}}}}}}\"}",
            ["Content-type" : ["application/json"], "Authorization":["Bearer ${tok}".toString()]]
    ) 
    { 
        response ->
        if (response.code >= 400)
            throw new com.exalate.api.exception.IssueTrackerException("Failed")
        else
            response.body as String
    }
    json = js.parseText(res)


    json.data.node?.fields?.nodes.each {
        if (it.name == "Status"){
            fieldId = it.id     
            it.options.each{
               statusEntry ->
               if (statusEntry.name == statusJira)
                            statusId = statusEntry.id
            }
        }
    }

    res = new GroovyHttpClient(nodeHelper)
        .http(
            "POST",
            "https://api.github.com/graphql",
            "{\"query\":\"mutation {updateProjectV2ItemFieldValue( input: { projectId: \\\"${projectId}\\\" itemId: \\\"${itemId}\\\" fieldId: \\\"${fieldId}\\\" value: { singleSelectOptionId: \\\"${statusId}\\\" }}) { projectV2Item { id }}}\"}",
            ["Content-type" : ["application/json"], "Authorization":["Bearer ${tok}".toString()]]
    ) 
    { 
        response ->
        if (response.code >= 400)
            throw new com.exalate.api.exception.IssueTrackerException("Failed")
        else
            response.body as String
    }
}




issue.labels       = replica.labels
issue.assignee     = nodeHelper.getUserByUsername(replica.assignee?.username)
issue.reporter     = nodeHelper.getUserByUsername(replica.reporter?.username)

Thanks, Dhiren

1 Like