Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

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!

 

 

20 comments

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 

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

Like Benz Kek _Adaptavist_ likes this

Perfect! It's viewable now, many thanks! 

Dirk Ronsmans Community Leader Mar 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 :)

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?

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.

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

Like Dirk Ronsmans likes this
Dirk Ronsmans Community Leader Jun 03, 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 Jun 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

@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.

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

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 :)

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. 

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 :))

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.

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!

@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 Oct 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 Oct 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?

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.

Comment

Log in or Sign up to comment
TAGS
Community showcase
Published in Apps & Integrations

🍻🍂Apptoberfest Update: Upcoming Virtual Events 🎉

Hello Community! I hope you've been enjoying the 🍂Apptoberfestivities🍂 (I know I have!) The event is heating up next week with a series of virtual events that we're calling the 🍻🍂Partner App ...

584 views 5 21
Read article

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you