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

Work with your Jira plugin from Adaptivist ScriptRunner or how to avoid code duplication

In this article I would like to discuss code duplication in ScriptRunner scripts.

When you begin to write scripts in ScriptRunner you usually put all the required code in a script and then attach the script to a post-function, validator etc. Everything goes smoothly until you realize that you made a mistake in your code or your requirements changed and now you have to find all the scripts with the related code. You can make a search on the groovy script folder and replace the old value with the new value but in my experience such a replacement will cause a bug somewhere in your code. Moreover if you did not test your code with the following feature of ScriptRunner:

https://scriptrunner.adaptavist.com/latest/jira/testing.html

or some other way, I guess you are in trouble.

That is the classic problem with code duplication. Let's try to solve the problem in ScriptRunner.

Part 1

I think the easiest way to do it is to create a helper class and then call it from a groovy script.

First we create our helper class in scripts/groovy/util directory with the name UtilHelper.groovy:

package groovy.util;

public class UtilHelper {
   public static String getMessage() {
    return "util helper message";
   }
}

Then we will create a post function script in scripts/groovy with the name postfunction.groovy where we call getMessage() function from our helper class:

package groovy

import groovy.util.UtilHelper;

log.error(UtilHelper.getMessage())

Just for the sake of simplification we will not change any workflow to run the post function. We will execute the script from the Script Console:

 runconsole.png

We can see the message from UtilHelper.groovy file. It worked.

Now I would like to discuss some problems with this approach:

1. Our team tried this approach on older versions of ScriptRunner and we had the following problems:

1.1. We had a static compilation error saying that our imported helper classes could not be found. But it worked in runtime.

1.2 If we made changes to a helper class then the changes were not visible in the calling script unless we also made changes to the calling script. This error did not let us automate script deployment between Jira instances.

2. I currently tried to use this approach with ScriptRunner 5.2.2. I did not have the errors above but:

2.1. in Script Console if I call the helper class from the inline script editor then sometimes my helper class can not be found.

2.2. I first saved my class in the scripts/groovy folder and then moved it into the scripts/groovy/util folder. Even if the file does not exist anymore in the scripts/groovy folder I still can call it.

I am sure that any of the behaviours above could be explained, but problems are problems and that is why I would like to offer another solution which also has a couple of other advantages.

Part 2

 Just imagine if we could have a common Jira plugin library which we could invoke from our Jira plugins or Scriptrunner scripts. In this case we could have all code in our common library and make changes only to the library.

Or let's say we want to implement feature toggle functionality (you can read more about feature toggle here). We could save all our toggles in a property file and then take all toggles from the file. But it would be better, if we could create a web item somewhere in the add-ons menu, and then call our webwork on the web item click. We could develop a plugin, which would manage the UI part of the managing our toggles functionality, and provide an interface to other plugins or Scriptrunner scripts to retrieve our toggles.

In my previous article I described how to make a core plugin, which could be called from another plugin. The name of the core plugin was jira-library. Let's just try to call jira-library plugin from a Scriptrunner script. If we can make it then we can write our feature toggle plugin as well.

We will call the getLibraryMessage() function from jira-library plugin.

You can download the plugin from here, make a package with atlas-mvn package command and install it to your Jira instance.

You can read about working with other plugin from ScriptRunner here.

I do not think I can explain how it works better than in ScriptRunner's documentation but the most important parts are WithPlugin annotation and PluginModule annotation. In the @WithPlugin we provide jira-library key. It will let us import classes from jira-library plugin. @PluginModule let us inject exported services from jira-library.

Keeping this in mind we open Script Console and write the following script:

import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import ru.matveev.alexey.tutorial.library.api.LibraryService

@WithPlugin("ru.matveev.alexey.tutorial.library.jira-library")

@PluginModule
LibraryService libraryService

log.error(libraryService.getLibraryMessage()

And after we run it, we will see the message from getLibraryMessage() function of jira-library:

withplugin.png

That is it. It worked.

Now about problems.

Our team has encountered only one problem so far. This approach does not work in ScriptRunner behaviours. We published this bug in Adaptivist Service Desk. Hopefully it will be fixed.

 

 

 

4 comments

I managed to solve the problem with behaviours in ScriptRunner 5.2.2 and 5.3.1. The behaviour script would look like this

import com.onresolve.scriptrunner.runner.customisers.PluginModule
import com.onresolve.scriptrunner.runner.customisers.WithPlugin
import com.onresolve.scriptrunner.runner.ScriptRunnerImpl
import com.onresolve.jira.groovy.user.FieldBehaviours
import ru.matveev.alexey.tutorial.library.api.LibraryService

@WithPlugin("ru.matveev.alexey.tutorial.library.jira-library")

public class MyBehaviour extends FieldBehaviours {
public void runTest() {
LibraryService ls = ScriptRunnerImpl.getPluginComponent(LibraryService)
log.error(ls.getLibraryMessage()

)
}

There is also a problem with import in inline scripts. I solved it like this:

import com.atlassian.jira.component.ComponentAccessor

def parent = getClass().getClassLoader()
def loader = new GroovyClassLoader(parent)
def utilHelper = loader.parseClass(new File('groovy/util/UtilHelper.groovy').text);

log.error(utilHelper.getMessage())

Hi @Alexey Matveev _cPrime_.

Just tried to clone an example of code and run using Atlas-run the code from your's repo example with the ScriptRunner for jira server in behaviors, but getting exect the same error in logs that i have in my code (troubles with calass import):

 

2019-11-18 19:31:50,848 http-nio-2990-exec-2 ERROR admin 1171x1095x1 1czyz8q 0:0:0:0:0:0:0:1 /rest/scriptrunner/behaviours/latest/runvalidator.json [common.error.jersey.ThrowableExceptionMapper] Uncaught exception thrown by REST service: startup failed:
[INFO] [talledLocalContainer] Script1.groovy: 5: unable to resolve class com.aval.jira.plugins.api.CustomerDAO
[INFO] [talledLocalContainer] @ line 5, column 1.
[INFO] [talledLocalContainer] import com.aval.jira.plugins.api.CustomerDAO
[INFO] [talledLocalContainer] ^
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer] 1 error
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer] org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
[INFO] [talledLocalContainer] Script1.groovy: 5: unable to resolve class com.aval.jira.plugins.api.CustomerDAO
[INFO] [talledLocalContainer] @ line 5, column 1.
[INFO] [talledLocalContainer] import com.aval.jira.plugins.api.CustomerDAO
[INFO] [talledLocalContainer] ^
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer] 1 error

 

Is that the same issue that you faced in behaviours?  Could you please share the link to the raised issue in scriptrunners helpdesk? Was it solved?

 

Thanks in advance.

Best regards,

Evgeniy

Comment

Log in or Sign up to comment
TAGS

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