Interacting with Insight objects from Scriptrunner Server/Data-center

With the availability of Insight with all JSM Service Desk subscriptions, I'm seeing an uptick in community questions about ScriptRunner and Insight Custom fields and Insight objects.

As a long-time ScriptRrunner user, when we purchased Insight last year for our new JSM implementation, I started exploring their API and figuring out how to leverage scriptrunner for many different automation needs we had.

The Insight API and data model is quite complex and attempting to build a simple rule or condition based on the value of an attribute in a selected object in an Insight custom field can require many many lines of code.

Over the months, I've developed a fairly wide range of functions that make interractions with insight a breeze.

Here is an example...

Say you want to show/hide some fields using Behaviour base on an attribute of object selected in a custom field...

Behaviour will give you a string representation of the ObjectKey for the selected object.

So, you will need to

  1. use the ObjectFacade to get an actual ObjectBean,
  2. determine the ObjectType
  3. find the Attribute by name for the ObjectType
  4. get the value for that attribute from the object

It would look something like this:

import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectFacade
import com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeFacade
import com.riadalabs.jira.plugins.insight.channel.external.api.facade.ObjectTypeAttributeFacade
@WithPlugin('com.riadalabs.jira.plugins.insight')
@PluginModule ObjectFacade objectFacade
@PluginModule ObjectTypeFacade objectTypeFacade
@PluginModule ObjectTypeAttributeFacade objectTypeAttributeFacade

def attributeName = 'My Attribute Name'
def insightFormField = getFieldById(fieldChanged)
String objectKey = insightFormField.value //this assumes this insight custom field doesn't allow multiple

def object = objectFacade.loadObjectBean(objectKey)
def objectType = objectTypeFacade.loadObjectType(object.objectTypeId)
def attributeBean = objectTypeAttributeFacade.findObjectTypeAttributeBeans(objectType.id).find{ it.name == attributeName}
def attributeValue = object.objectAttributeBeans.find{it.objectTypeAttributeId == attributeBean.id}?.objectAttributeValueBeans*.value

And you haven't even started to write the behaviour logic yet ... all that just to get a simple attribute value. And only if that attribute is a simple type like text.

What if the attribute I'm interested in is not directly in the selected object but in one of its outbound reference? 

For example, imagine a User object where the Manager attribute points to another object and I'm interested in looking up the title of the manager for the selected User.

What if I told you that this is all I have to write in my behaviour scripts to get the manager's title?

import com.qad.common.jira.utils.InsightUtils
def userFormField = getFieldById(fieldChanged)
def managerTitle = InsightUtils.getAttributeValueFromDotNotation(userFormField.value, 'Manager.Title')

Much simpler yeah?

What about having some lookup in a script (either behaviour or postfunction) in the Customer portal? You might have come across an issue where the portal user doesn't have access to view the insight objects. You might want to be able to perform some script-level lookup as a different user.

Well, that is all possible using this utility class that I've developed and I am now sharing with the community:

https://bitbucket.org/peter_dave_sheehan/groovy/src/master/jiraserver/insightUtils/

Obviously, since this is provided for free, there is no guarantee this will work for you in all your use cases or that I will help with support.
But if you get into troubles, tag me in a community post and I might be able to help.

Have fun scripting!

 

 

29 comments

Comment

Log in or Sign up to comment
Benz Kek _Adaptavist_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 28, 2021

Hi Peter-Dave, this is awesome! I don't think your InsightUtils is made public yet, not able to see the content of your BitBucket link. :D 

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 28, 2021

Thanks for the heads-up @Benz Kek _Adaptavist_  please check it now. 

Like Benz Kek _Adaptavist_ likes this
Benz Kek _Adaptavist_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 28, 2021

Perfect! It's viewable now, many thanks! 

Dirk Ronsmans
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 29, 2021

Awesome work! I always hate the 10 hoops you need to jump through to make something "easy".

You're really taking the sting out of this one :)

Guillaume May 12, 2021

It is working fine :-)

//
// Read objet 
//

def myObject = InsightUtils.getObjectByKey('CMDBNXO2-14780')
String ville = InsightUtils.getAttributeValueFromDotNotation(myObject, "SITES.Ville")
log.warn ("Ville : " + ville)

//
// Read attributs 
//

Map myMap = InsightUtils.getAllAttributesAsKeyValuePairs(myObject)
myMap.each{ attribut, valeur -> log.warn "${attribut} : ${valeur}" }

Question?

Is there a way to create an object?

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
May 12, 2021

I've only had 1 use case for creating an object from code.... and it was quite specific and embeded into a larger script. I did not attempt to generalize it for including in this class.

I will update here and in the repository if/when I create that case.

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 3, 2021

For anyone following this thread, I've added a createObject() method.

Like # people like this
Dirk Ronsmans
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 3, 2021

I can't begin to quantify how much time and annoyance your code has saved me :)

Like Peter-Dave Sheehan likes this
Dirk Ronsmans
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 23, 2021

Hey @Peter-Dave Sheehan ,

I noticed for the getAttributeValueFromDotNotation() method that when you return a single reference the returntype is different from when there are multiple ones (I believe an ArrayList vs a String)

What's the idea behind that? Seems a bit counter intuitive on how you need to then process the return result

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 23, 2021

@Dirk Ronsmans if that's the case, it wasn't really intentional. I think it's likely a result of the evolution of the functionality.

Initially, reference attributes just returned the label. Not terribly useful. 

So I added a 3rd parameter:

Boolean returnObject = false

If you change the method declaration to set the default to 'true', you will always return a single ObjectBean when the attribute you call is a reference. 

I also later added another method called getObjectReferenceFromDotNotation that doesn't have that third parameter but calls the getAttributeValueFromDotNation with the parameter set to true.

The ability to correctly return fields that allow multiple values came later. In fact, looking at the code now, I see that it should return either the label or the objectBean based on that parameter that panned out in my testing.

So that leads me to think that perhaps what you've noticed is that whatever the data type of the attribute, if there are many values, you will get an ArrayList, but if there is a single value in the array, I return the first and only item. I can't quite remember what the reasoning for that was.

I think in part is because every attribute was returning an ArrayList even for attributes that don't allow multiple values.

I think a nice improvement would be to look up the upper limit of the attribute definition and if it's 1, then return a single item, otherwise return an array (either empty array, array of 1, or array of many)

If you have a more concrete example, I'd be happy to review it.

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
August 12, 2021

For anyone watching this ... I happened to run across this alternative https://github.com/Riada-AB/InsightManager

It seems to achieve many of the same features, has a few methods that I've never needed or developed, and proper functional test. It is also cleaner, better documented, and more thought-through than mine.
And being from the company that initially created Insight, it's likely of better quality.

It is, however, missing my favorite method: "getAttributeValueFromDotNotation". And a couple of others I need (like clone and move).

But I guess nobody is perfect.

Like Dirk Ronsmans likes this
Dirk Ronsmans
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
August 13, 2021

It does look like it's still actively being maintained too so that's a plus.

I gotta agree about the getAttributeValueFromDotNotation method, that has to be the one that I use the most :) 

If you believe that project's code to be cleaner perhaps we can nudge the dev to consider creating such a method?

He does have the getObjectAttributeValues method which I guess you could iterate over? Still a big fan of your DotNotation approach tho :)

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
August 13, 2021

I couldn't resist ... forked the project and modified the getObjectAttributeValues() to recognized dotNotation Attribute and recursively call itself.

I created a pull request. We'll see if they like it.

I'll still keep my own implementation. For one, theirs is mentioning deprecating the ability to elevate privileges and that's a critical feature for me (though my implementation is not global but at each method call level). And a few other purpose-built things I have. 

But still a good alternative for anyone getting started. 

Dirk Ronsmans
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 20, 2021

Hey @Peter-Dave Sheehan ,

I wonder if something went wrong with the last version on bitbucket..

When I take the current script I get the following error(s):

The script could not be compiled:
<pre>org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
file:InsightUtils.groovy: 622: unable to resolve class CreateException @ line 622, column 19. throw new CreateException(attributeValidationResult.messages as String) ^
file:InsightUtils.groovy: 657: unable to resolve class ObjectAttributeBean @ line 657, column 76. alueBean> getObjectValueBeans(ObjectAttr ^ file:InsightUtils.groovy: 657: unable to resolve class ObjectAttributeValueBean @ line 657, column 30. private static ArrayList<ObjectAttributeValueBean> getObjectValueBeans(ObjectAttributeBean oaBean, ObjectTypeAttributeBean otaBean, Object value, Boolean allowObjectReferenceCreate) { ^ file:InsightUtils.groovy: 672: unable to resolve class Project @ line 672, column 37. } else if (value instanceof Project) { ^ 4 errors </pre>.

Any idea what it could be?

When I comment out the last block (below the StoreObject) from line 595  it works fine (but then I don't have a createObject :))

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 20, 2021

Hi @Dirk Ronsmans 

You are right... looks like I missed a few imports whenI copied these methods from my own implementation to the one for sharing on bitbucket.

Add these 4 imports:

import com.atlassian.jira.project.Project
import com.atlassian.jira.exception.CreateException
import com.riadalabs.jira.plugins.insight.services.model.ObjectAttributeBean
import com.riadalabs.jira.plugins.insight.services.model.ObjectAttributeValueBean

I also re-published the whole file with a few other fixes to reduce the number of static type checking errors.

Dirk Ronsmans
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 21, 2021

Awesome! The create indeed seems to work now :)

I did encounter another issue with the new version:

org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '[1]' with class 'java.util.ArrayList' to class 'java.lang.Integer'
at pf.global.InsightUtils$_getAttributeValueFromDotNotation_closure6.doCall(InsightUtils.groovy:252)
at pf.global.InsightUtils.getAttributeValueFromDotNotation(InsightUtils.groovy:209)
at pf.global.InsightUtils$_getAttributeValueFromDotNotation_closure7.doCall(InsightUtils.groovy:283)
at pf.global.InsightUtils.getAttributeValueFromDotNotation(InsightUtils.groovy:279)
at pf.global.InsightUtils.getAttributeValueFromDotNotation(InsightUtils.groovy)
at pf.global.InsightUtils$getAttributeValueFromDotNotation.call(Unknown Source)

Seems that line 252 now casts the returnValue as Integer which is failing.

returnValue = configureFacade.loadStatusTypeBean(returnValue as Integer).name

I changed that back to 

returnValue = configureFacade.loadStatusTypeBean(returnValue).name

and it's running again :)

Could be due to versions of course (I'm running 8.6.5 at the moment)

Btw, @Peter-Dave Sheehan if you prefer another way to log these "issues" or discuss it feel free to let me know!

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 21, 2021

@Dirk Ronsmans I don't officially support this code. It's shared as a starting point for the community. So I don't want to provide any official means for issues to be reported.

That being said, I don't mind making small updates on the basis of what you've found. So this post is as good a place as any to report it.

In this case, I found the real reason why the static type checking failed and why attempting to convert to either Integer or even int would not work. It's because the returnValue in this case was a list. Even though status attributes can't contain more than one value, the underlying model still stores it as a list of 1.

I updated the files in bitbucket.

Like Dirk Ronsmans likes this
Dirk Ronsmans
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 22, 2021

@Peter-Dave Sheehan ,

Absolutely understand, just though you might want a more "structured" way to get feedback on your code :)

it's already a great starting off point and basically covers 99% of my use cases so I'm happy.

Dirk Ronsmans
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 22, 2021

@Peter-Dave Sheehan last question for a while, I promise ;-)

I've tried to use the library from an Automation within Insight. From the documentation I understand that I need to add some code to import the class in my automation script.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.util.JiraHome
def jiraHome = ComponentAccessor.getComponentOfType(JiraHome.class).home
Class InsightUtils = new GroovyClassLoader(getClass().getClassLoader()).parseClass(new File("$jiraHome/path/to/scripts/InsightUtils.groovy"))

That works fine but when it tries to load the script it fails on the scriptrunner imports within the InsightUtils.groovy file.

InsightUtils.groovy: 27: unable to resolve class com.onresolve.scriptrunner.runner.customisers.PluginModule
@ line 27, column 1.
import com.onresolve.scriptrunner.runner.customisers.PluginModule
^

C:\Program Files\Atlassian\Application Data\Jira\scripts\pf\global\InsightUtils.groovy: 28: unable to resolve class com.onresolve.scriptrunner.runner.customisers.WithPlugin
@ line 28, column 1.
import com.onresolve.scriptrunner.runner.customisers.WithPlugin

Is there another additional step required for it to load?

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
October 22, 2021

In my experience, other groovy implementation outside of scriptrunner doesn't allow you to pick up custom classes built with/for scriptrunner.

You will see in bitbucket, I've explained the situation a little bit in the ReadMe.

In this case, you'll want to use this version of the file: https://bitbucket.org/peter_dave_sheehan/groovy/src/master/jiraserver/insightUtils/insight/InsightUtils.groovy

Then, in your insight automation script, you will need to load the class dynamically with some code like:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.config.util.JiraHome
def jiraHome = ComponentAccessor.getComponentOfType(JiraHome.class).home
Class InsightUtils = new GroovyClassLoader(getClass().getClassLoader()).parseClass(new File("$jiraHome/path/to/scripts/InsightUtils.groovy"))

You'll want to use this sparingly for large classes or large volumes of automation because there is no caching of the compiled class and it's loaded and compiled with each execution.

Tomáš Vrabec November 24, 2021
String objectKey = insightFormField.value //this assumes this insight custom field doesn't allow multiple

Hi guys. I just run into troubles with multiple values in insight field and behaviors.

Sorry for interrupting this thread, but looks there is someone active.

All methods I tried ending in null value at output. 

EDIT: Forget this comment, customer affected by https://productsupport.adaptavist.com/browse/SRJIRA-5329

Like Peter-Dave Sheehan likes this
Will C
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 12, 2022

Hi there, basic question but how do we import this class into intellij or scriptrunner on Jira?

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
January 12, 2022

If you store the file in your scriptrunner script root: e.g. <jira-home>/scripts/com/acme/scripts/helpers/InsightUtils.groovy)

Then in any other script runner script in jira, you just access it with

import com.acme.scripts.helpers.InsightUtils
InsightUtils.getObjectByKey('KEY-123')

The same with intellij, just save the groovy class file in your intellij project in a source files directory, and you should be able to refer to it by package.ClassName

Like # people like this
Piyush A (STR)
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
November 22, 2022

Do we have notes for this related to Cloud?

David Harkins
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
September 15, 2023

Many Thanks for this, has saved hours of work, speeding things up dramatically.

I have how ever come across an issue which I've not been able to figure out.

When creating a new object which has an attribute of type 'Group', how should the attributeValueMap be populated?

An array collection of group objects fails, as does an array of string group names.

Any assistance would be most welcome :-)

Like Gibson Lam likes this
TAGS
AUG Leaders

Atlassian Community Events