1 answer
- 210
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,
- Charlie Misonne
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)
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
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 known when 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
Because I'm always getting Name or service not known when 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)
I will have a look into the ticket and revert here.
- Joachim Bollen
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<String, List<String>> 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<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.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
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.
- 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
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://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() - This does not work
- Charlie Misonne
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...
Add your comment...
I'd like to see how the sync of Insight Cloud fields works? The documentation only says Jira on-prem.
Cheers,