Is insight compatible on Jira cloud sync?

Originally asked by George Smith on 21 January 2022 (original question)


I’d like to see how the sync of Insight Cloud fields works? The documentation only says Jira on-prem.

Cheers,


Answer by Joachim Bollen on 25 January 2022

Hi George,

The basic issuedata contains all you need to get this to work with some additional scripting.

You’ll need to extract the workspaceId and the id of the object from the customfield:

workspaceId = issue.customfield."Insight Object".workspaceId
objectId = issue.customfield."Insight Object".objectId

After that, you can call the Insight API to get the full data for this object:

object = httpClient.get("https://api.atlassian.com/jsm/insight/workspace/" + workspaceId + "/v1/object/"+objectId)

After which you can add all attributes you need to the sync via something like

replica.insightObject = object.attributes[]

Let me know if this helps,


Comments:

Charlie Misonne commented on 27 January 2022

Hi Joachim,

Thanks! George created this question on my behalf.

I tried what you did but it doesn’t work for me yet.

We have an Insight custom field called Mobile OS. The Insight object has the same name.

Here is my code:

workspaceId = issue.customFields.'Mobile OS'?.value[0]?.workspaceId  
objectId = issue.customFields.'Mobile OS'?.value[0]?.objectId  
  
object = httpClient.get("<https://api.atlassian.com/jsm/insight/workspace/>" + workspaceId + "/v1/object/"+objectId)  
replica.insight."Mobile OS" = object.attributes[]

When syncing Exalate says *Name or service not known.*So I assume something is wrong when fetching those URLs.

First thing I noticed is you are using customfield but I think it should be customFields, or am I mistaken?

Without s gives me Cannot get property ‘Mobile OS’ on null object.

This in the local replica in Jira (I replaced the ID’s)

{
  "version": {
    "major": 1,
    "minor": 14,
    "patch": 0
  },
  "hubIssue": {
    "components": [],
    "attachments": [],
    "voters": [],
      "Mobile OS": {
        "id": 10063,
        "name": "Mobile OS",
        "uid": "10063",
        "type": "UNHANDLED",
        "value": [
          {
            "workspaceId": "1234567890",
            "id": "1234567890:33",
            "objectId": "33"
          }
        ]
      },
Francis Martens (Exalate) commented on 27 January 2022

Hi Charlie Misonne

Nice to meet you on the bitstream

replica.insight is not an object and when you do replica.insight.“Mobile OS”, groovy will not know what to do.

If you do what Joachim proposes, then exalate will create the object
So

// works
replica.city = "Brussels"
// doesn't work because country doesn't exists
replica.country.city = "Brussels"

FYI - you can check what the attributes are (returned by the httpClient)

debug.error("Object attributes are ${object.attributes}")

if you trigger an incoming sync, an error will be raised with the attributes

Charlie Misonne commented on 24 February 2022

Hi Francis Martens (Exalate) !

Thank for pointing out my mistake about tha variable.

However, fetching the values from Insight is still not working. I keep getting name or service not knownwhen calling https://api.atlassian.com/...

I have an ongoing support ticket about this but so far I’m out of luck: EASE-12717

I wonder if this can be a network restriction in the plugin?

cheers!

Charlie

Charlie Misonne commented on 09 March 2022

Because I’m always getting Name or service not knownwhen trying to fetch the Insight values from the API I implemented a dumb workaround:

In the incoming script on the target application (Azure Devops) I create some If/Else statements checking for the Insight value ID. Each IF sets the string value of the field this way.

Francis Martens (Exalate) commented on 09 March 2022

I will have a look into the ticket and revert here.

Joachim Bollen commented on 23 March 2022

Hi Charlie Misonne ,

My previous answer was a bit short in mentioning the httpClient. By default in Exalate for Jira Cloud, the httpClient will prepend all requests with the base Url of Jira, which is not what you need in this case.

The code I used to build a customized http client:

def http = { String method, String path, String workspaceId, String queryParams, String body, Map> headers ->
    final def injector = play.api.Play$.MODULE$.current().injector()

    def orNull = { scala.Option opt -> opt.isDefined() ? opt.get() : null }
    def pair = { l, r -> scala.Tuple2$.MODULE$., ?apply(l, r) }
    def none = { scala.Option$.MODULE$. empty() }
    def getGeneralSettings = {
        def gsp = injector.instanceOf(com.exalate.api.persistence.issuetracker.jcloud.IJCloudGeneralSettingsPersistence.class)
        def gsOpt = await(gsp.get())
        def gs = orNull(gsOpt)
        gs
    }
    final def gs = getGeneralSettings()

    def removeTailingSlash = { String str -> str.trim().replace("/+\$", "") }
    final def insightRestApiUrl = "https://api.atlassian.com/jsm/insight/workspace/" + workspaceId

    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])
                //noinspection GrUnresolvedAccess
                parsedUri.metaClass.mailMap = [
                        recipient: schemeSpecificPartList[0],
                        cc       : tempMailMap.find { //noinspection GrUnresolvedAccess
                            it.key.toLowerCase() == 'cc' }.value,
                        bcc      : tempMailMap.find { //noinspection GrUnresolvedAccess
                            it.key.toLowerCase() == 'bcc' }.value,
                        subject  : tempMailMap.find { //noinspection GrUnresolvedAccess
                            it.key.toLowerCase() == 'subject' }.value,
                        body     : tempMailMap.find { //noinspection GrUnresolvedAccess
                            it.key.toLowerCase() == 'body' }.value
                ]
            }
            if (parsedUri.fragment?.contains('?')) { // handle both fragment and query string
                //noinspection GrUnresolvedAccess
                parsedUri.metaClass.rawQuery = parsedUri.rawFragment.split('\\?')[1]
                //noinspection GrUnresolvedAccess
                parsedUri.metaClass.query = parsedUri.fragment.split('\\?')[1]
                //noinspection GrUnresolvedAccess
                parsedUri.metaClass.rawFragment = parsedUri.rawFragment.split('\\?')[0]
                //noinspection GrUnresolvedAccess
                parsedUri.metaClass.fragment = parsedUri.fragment.split('\\?')[0]
            }
            if (parsedUri.rawQuery) {
                //noinspection GrUnresolvedAccess
                parsedUri.metaClass.queryMap = parseQueryString(parsedUri.rawQuery)
            } else {
                //noinspection GrUnresolvedAccess
                parsedUri.metaClass.queryMap = null
            }

            //noinspection GrUnresolvedAccess
            if (parsedUri.queryMap) {
                //noinspection GrUnresolvedAccess
                parsedUri.queryMap.keySet().each { key ->
                    def value = parsedUri.queryMap[key]
                    //noinspection GrUnresolvedAccess
                    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
    }

    def unsanitizedUrl = insightRestApiUrl + path
    def parsedUri = parseUri(unsanitizedUrl)

    //noinspection GrUnresolvedAccess
    def embeddedQueryParams = parsedUri.queryMap


    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)
    if (queryParams) {
        sanitizedUrl = sanitizedUrl + "?" + queryParams
    }
    def response
    try {
        def request = httpClient
                .ws()
                .url(sanitizedUrl)
                .withMethod(method)

        if (headers != null && !headers.isEmpty()) {
            def scalaHeaders = scala.collection.JavaConversions.asScalaBuffer(
                    headers.entrySet().inject([] as List>) { List> result, kv ->
                        kv.value.each { v -> result.add(pair(kv.key, v) as scala.Tuple2) }
                        result
                    }
            )
            request = request.withHeaders(scalaHeaders)
        }

        if (body != null) {
            def writable = play.api.http.Writeable$.MODULE$.wString(play.api.mvc.Codec.utf_8())
            request = request.withBody(body, writable)
        }

        response = await(request.execute())


    } catch (Exception e) {
        throw new com.exalate.api.exception.IssueTrackerException("Unable to perform the request $method $path, please contact Exalate Support: ".toString() + e.message, e)
    }
    if (response.status() >= 300 && response.status() != 400) {
    //if (response.status() >= 300) {
        throw new com.exalate.api.exception.IssueTrackerException("Failed to perform the request $method $path ${body ? "with body `$body`".toString() : ""}(status ${response.status()}), please contact Exalate Support: ".toString() + response.body())
    }
    response.body() as String
}


And the request to Insight itself:

def js = new groovy.json.JsonSlurper()

def response = js.parseText(http(
        "GET",
        "/v1/object/" + objectId,
        workspaceId,
        null,
        null,
        [
                "Authorization":["Bearer ${token}".toString()],
                "Content-Type":["application/json"],
        ]
))

Hope this clarifies it a bit.

Charlie Misonne commented on 21 November 2022

Hi Joachim,

I finally got the time to take a look at this because it was planned in our current sprint.

First I tried to put code with the customized http client in the incoming Azure Devops script but I noticed the injector is not available.

So I’m doing this in the Jira outgoing script now.

  1. This does not work
final def injector = play.api.Play$.MODULE$.current().injector()

I think injector is already available in the script so I removed that line
2. ```
def gsp = injector.instanceOf(com.exalate.api.persistence.issuetracker.jcloud.IJCloudGeneralSettingsPersistence.class)
def gsOpt = await(gsp.get())



I'm getting this on **await**:  
No signature of method: org.codehaus.groovy.jsr223\.GroovyScriptEngineImpl.await() is applicable for argument types: (scala.concurrent.impl.Promise$DefaultPromise) values: \[Future(Success(Some(JCloudGeneralSettings(Some(1\),Some([https://jcloudnode\-fogo\-loft\-blan\-pear.exalate.cloud](https://jcloudnode-fogo-loft-blan-pear.exalate.cloud)),[https://dcsecosystem.atlassian.net,Some(ATCOzLwOqmOWHlr1p8E6Qh0HBbHHxjdhhMxnmp34MuqqO2cRDBbR6k2IV2WKRiFu13CqamY7u2KFKPBEAj3\_nY\_BtA3D001E05\),Some(eyJob3N0S2V5IjoiYjVmMmRmNDEtN2I4NC0zNDljLTk5MjgtY2MwNThmYWYxZTJhIiwiYWRkb25LZXkiOiJjb20uZXhhbGF0ZS5qaXJhbm9kZSJ9\),Some(b5f2df41\-7b84\-349c\-9928\-cc058faf1e2a),Some(MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKQUn5qivi6JzLEzKLbhcuHYpSSjg08Xe5Apd9F2EZPsXWjxgLqfh56Sow6KokVRSMVDQqf\+Dby5SBfpbkttZnZczOI92nMYAAhAHb9mTVngj9SEj6rhLD\+4MINgjH5EA9kvFJPSbBVFQqWkVmJ/aB6J1dP9BpFBY/ZvVyo18MrwIDAQAB),true,true,false,true,true,false](https://dcsecosystem.atlassian.net,Some(ATCOzLwOqmOWHlr1p8E6Qh0HBbHHxjdhhMxnmp34MuqqO2cRDBbR6k2IV2WKRiFu13CqamY7u2KFKPBEAj3_nY_BtA3D001E05),Some(eyJob3N0S2V5IjoiYjVmMmRmNDEtN2I4NC0zNDljLTk5MjgtY2MwNThmYWYxZTJhIiwiYWRkb25LZXkiOiJjb20uZXhhbGF0ZS5qaXJhbm9kZSJ9),Some(b5f2df41-7b84-349c-9928-cc058faf1e2a),Some(MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKQUn5qivi6JzLEzKLbhcuHYpSSjg08Xe5Apd9F2EZPsXWjxgLqfh56Sow6KokVRSMVDQqf+Dby5SBfpbkttZnZczOI92nMYAAhAHb9mTVngj9SEj6rhLD+4MINgjH5EA9kvFJPSbBVFQqWkVmJ/aB6J1dP9BpFBY/ZvVyo18MrwIDAQAB),true,true,false,true,true,false)))))] Possible solutions: wait(), wait(long), wait(long, int), split(groovy.lang.Closure), with(groovy.lang.Closure), any()
Charlie Misonne commented on 12 December 2022

Hi Joachim Bollen

Perhaps you missed my previous comment.

The script you provided does not seem to work. Does it work for you seemless on Jira Cloud?

Perhaps we are on different Exalate versions…