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,
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,
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"
}
]
},
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
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
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.
I will have a look into the ticket and revert here.
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.
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.
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()
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…