Make your custom SIL routines portable between Jira versions!

Hello!

In this article I would like continue my story on developing your own custom routines for the SIL engine. 

You can find my pervious article here.

In this article we will talk how to make your custom routines portable between Jira versions. 

One of the main advantages of SIL is: code once written, can be run on all Jira versions. We will make sure that our custom SIL routines also run on all Jira versions, supported by the SIL engine.

Create add-on from the sil extension archetype

Clone sil-extension-archtype:

git clone https://alex1mmm@bitbucket.org/alex1mmm/sil-extension-archetype.git --branch v1 --single-branch

Install this archetype into your local maven repository and create an add-on using this archetype. You can find detailed information on how to do it here.

Inject SIL engine exported services

If we want to make our routines portable between Jira version, we have to inject services provided by the SIL engine. It will make sure that our plugin is portable. It is as easy as this!

But how do we know what services are exported by the SIL engine?

First install the SIL engine and then open the following url in your Jira:

http://localhost:2990/jira/plugins/servlet/upm/osgi

You will see OSGI information of all add-ons in your Jira:

Screenshot 2020-03-20 at 12.51.17.pngNow find the KATL Commons plugin and open the Registered services node:

Screenshot 2020-03-20 at 12.54.10.png

JMUserServices, JMProjectServices and so on are the exported beans.

Now we now all exported beans. You can guess, what each bean does by the name of a bean.

Look at the code in your created from the archetype add-on

Now we now how to retrieve information about all exported services in the SIL engine. Let's use these services!

BeanService.java

@Named
public class BeanService {
@Getter
private final KIssueService kIssueService;
@Getter
private final ClassLoaderService classLoaderService;
@Getter
private final CurrentUserHelper currentUserHelper;
@Getter
private final JMIssueSearchServices jmIssueSearchServices;
@Getter
private final UserHelper userHelper;
@Getter
private final JMIssueServices jmIssueServices;
@Inject
public BeanService(@ComponentImport KIssueService kIssueService,
@ComponentImport CurrentUserHelper currentUserHelper,
@ComponentImport JMIssueSearchServices jmIssueSearchServices,
@ComponentImport UserHelper userHelper,
@ComponentImport JMIssueServices jmIssueServices,
ClassLoaderService classLoaderService) {
this.kIssueService = kIssueService;
this.classLoaderService = classLoaderService;
this.currentUserHelper = currentUserHelper;
this.jmIssueSearchServices = jmIssueSearchServices;
this.userHelper = userHelper;
this.jmIssueServices = jmIssueServices;
}
}

 Here is our spring bean, which contains all injected services. I tend to create such beans, so that I do not have to inject the same beans in all my routines.

Let's have a closer look at this line:

@Getter
private final KIssueService kIssueService;

@Getter is a Lombok annotation to create the get method for kIssueService class property.

We create properties in our class for all services, which we will inject. And then we inject all services in the constructor:

    public BeanService(@ComponentImport KIssueService kIssueService,
@ComponentImport CurrentUserHelper currentUserHelper,
@ComponentImport JMIssueSearchServices jmIssueSearchServices,
@ComponentImport UserHelper userHelper,
@ComponentImport JMIssueServices jmIssueServices,
ClassLoaderService classLoaderService) {
this.kIssueService = kIssueService;
this.classLoaderService = classLoaderService;
this.currentUserHelper = currentUserHelper;
this.jmIssueSearchServices = jmIssueSearchServices;
this.userHelper = userHelper;
this.jmIssueServices = jmIssueServices;
}

Done!

Now inject the BeanService to ESLauncher class and pass this bean as a parameter in routines.

/* create BeanService property */
private final
BeanService beanService;

@Inject
public ESLauncher(@ComponentImport EventPublisher eventPublisher,
PluginInfoService pluginInfoService,
@ComponentImport CommonPluginConfigurationService commonPluginConfigurationService,
@ComponentImport HostConfigurationProvider hostConfigurationProvider,
@ClasspathComponent PluginConfigurationServiceImpl pluginConfigurationService,
/* Inject the BeanService service */
BeanService beanService)
{
super(eventPublisher, pluginInfoService, hostConfigurationProvider, pluginConfigurationService);

log.error("eslauncher constructor");
this.beanService = beanService;

}

@Override
public void doAtLaunch() {
super.doAtLaunch();
log.error("eslauncher doatlaunch");
RoutineRegistry.register(new SayHello( beanService,"SayHello"));
RoutineRegistry.register(new SayHello2( beanService,"SayHello2"));
RoutineRegistry.register(new SayHello3( beanService,"SayHello3"));

}

Now we can use our BeanService in all our routines.

SayHello.java

In SayHello.java we define a routine, which will accept a user email as the input parameter, check that the current user is a Jira administrator and if the user is a Jira administrator, all issues, which were reported by the user with the passed email, will be deleted.

Of course, if you want to implement this functionality, you do not need to create your own SIL routine. You could do it with a SIL code like this:

function deleteIssueForUser(string userEmail) {
if (isUserInGroup("jira-administrators", currentUserKey())) {
string[] issues = selectIssues("reporter = " + currentUserKey());
for (string issue in issues) {
deleteIssue(issue);
}
}
}

deleteIssueForUser("user@email.com");

But I want my example to be simple so that it could be easier to grasp the main ideas. That is why I made my custom routine to implement this logic.

@Slf4j
public class SayHello extends AbstractSILRoutine<MutableString> {
private static final SILType[][] types = {{ TypeInst.STRING }};
private final BeanService beanService;

public SayHello(BeanService beanService, String name) {
super(beanService.getClassLoaderService().getPluginClassLoader(), name, types);
this.beanService = beanService;
}

@Override
public SILType<MutableString> getReturnType() {
return TypeInst.STRING;
}


@Override
protected SILValue<MutableString> runRoutine(SILContext silContext, List<SILValue<?>> list) {
SILValue param = list.get(0);
String userEmail = param.toStringValue();
KIssueService kIssueService = beanService.getKIssueService();
CurrentUserHelper currentUserHelper = beanService.getCurrentUserHelper();
JMIssueSearchServices jmIssueSearchServices = beanService.getJmIssueSearchServices();
UserHelper userHelper = beanService.getUserHelper();
JMIssueServices jmIssueServices = beanService.getJmIssueServices();
ApplicationUser requestedUser = userHelper.getUserByEmail(userEmail);

if (currentUserHelper.isUserAdministrator()) {
SearchService.ParseResult parseResult = jmIssueSearchServices.getSearchService().parseQuery(requestedUser, "reporter = " + requestedUser.getKey());
List<Issue> issues = (List<Issue>) kIssueService.searchIssues(requestedUser, parseResult.getQuery());
issues.stream().forEach(issue -> kIssueService.deindexIssue(jmIssueServices.getIssueManager().getIssueByCurrentKey(issue.getKey())));
}
return SILValueFactory.string( "issues deleted");

}

@Override
public String getParams() {
return "(userEmail)";
}
}

The code of the routine is in the runRoutine Method:

    @Override
protected SILValue<MutableString> runRoutine(SILContext silContext, List<SILValue<?>> list) {

/* Get the user email parameter */

SILValue param = list.get(0);
String userEmail = param.toStringValue();

/* create local variables for all needed services */

KIssueService kIssueService = beanService.getKIssueService();
CurrentUserHelper currentUserHelper = beanService.getCurrentUserHelper();
JMIssueSearchServices jmIssueSearchServices = beanService.getJmIssueSearchServices();
UserHelper userHelper = beanService.getUserHelper();
JMIssueServices jmIssueServices = beanService.getJmIssueServices();

/* Use SIL UserHelper service to get ApplicationUser by email */

ApplicationUser requestedUser = userHelper.getUserByEmail(userEmail);

/* Use SIL CurrentUserHelper to check if the current user is a Jira admin */

if (currentUserHelper.isUserAdministrator()) {

/* Use SIL services to get issues by JQL query */

SearchService.ParseResult parseResult = jmIssueSearchServices.getSearchService().parseQuery(requestedUser, "reporter = " + requestedUser.getKey());
List<Issue> issues = (List<Issue>) kIssueService.searchIssues(requestedUser, parseResult.getQuery());

/* Delete all selected issues */

issues.stream().forEach(issue -> kIssueService.deindexIssue(jmIssueServices.getIssueManager().getIssueByCurrentKey(issue.getKey())));
}
return SILValueFactory.string( "issues deleted");

}

I made explanation in the source code.

That is all for this article. Now you know how to inject SIL services and you can make your custom SIL routines portable through Jira versions. 

1 comment

Taranjeet Singh
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 25, 2020

Nice work, @Alexey Matveev !

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events