Всем привет!

В этой статье продолжим разговор про разработку собственных функций для SIL. Предыдущую статью можно почитать вот здесь.

Одним из главных преимуществ SIL является то, что код, написанный на SIL, будет работать на всех версиях Jira, поэтому нам нужно сделать так, чтобы наши кастомные функции работали бы также.

Создаем плагин из sil-extension-archetype


Клонируем архетип:

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

Устанавливем архетип в локальный репозиторий и создаем плагин из этого архетипа. Подробнее, как это сделать, можно почитать вот тут.

Добавляем сервисы SIL


Для того, чтобы сделать нашу функцию переносимой между разными версиями Jira, мы будет использовать сервисы, которые предоставляет SIL. Т.е. мы будет проксировать наши вызовы к Jira Java API через SIL Java Api.

Но как узнать, какие сервисы предоставляет SIL?

Устанавливаем SIL и переходит по следующему адресу:

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

Вы увидите информацию OSGI по все плагинам в Jira:



Найдем в списке KATL Commons плагин и раскроем Registered services:



JMUserServices, JMProjectServices это сервисы, которые экспортирует SIL.

Теперь мы знаем, какие SIL сервисы доступны, и по названию сервиса мы можем догадаться о его предназначении.

Посмотрим код в созданном плагине


Теперь мы знаем какие сервисы нам доступны. Давайте используем их в нашем плагине.

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;
    }
}

Этот класс содержит все сервисы, которые мы будем использовать для наших функций. Я создаю отдельный такой класс для того, чтобы не дублировать обращение к сервисам в каждой функции.

Давайте посмотрим вот на эти строчки:

@Getter
 private final KIssueService kIssueService;

Getter это Lombok аннотация, которая создает get метод для свойства kIssueService.

Мы создаем свойства класса для каждого используемого сервиса. Далее мы получаем эти сервисы через конструктор.

    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;
    }

Готово.

Добавляем бин BeanService в класс ESLauncher и передаем этот бин в наши функции:

/* Создаем свойство BeanService */
private final BeanService beanService;

@Inject
public ESLauncher(@ComponentImport EventPublisher eventPublisher,
                  PluginInfoService pluginInfoService,
                  @ComponentImport CommonPluginConfigurationService commonPluginConfigurationService,
                  @ComponentImport HostConfigurationProvider hostConfigurationProvider,
                  @ClasspathComponent PluginConfigurationServiceImpl pluginConfigurationService,
/* Инжектим BeanService */
                  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"));

}

Теперь мы можем использовать BeanService в наших функциях.

SayHello.java


В SayHello.java мы сделаем функцию, которая будет принимать электронный адрес пользователя, проверять, что пользователь, который выполняет скрипт администратор, удалять все тикеты, которые были созданы пользователем, с переданным электронным адресом.

Конечно, мы можем сделать эту логику и без написания нашей собственной функции. Код на SIL будет выглядеть вот так:

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");

Но я хотел сделать небольшой пример, который бы показал основные принципы работы.

Вот текст класса:

@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)";
    }
}

Мы реализуем логику в методе runRoutine:

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

/* Получаем электронный адрес пользователя */

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

/* Создаем локальные переменные для всех сервисов SIL */

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

/* Используем SIL UserHelper, чтобы получить ApplicationUser по электронному адресу */

        ApplicationUser requestedUser = userHelper.getUserByEmail(userEmail);

/* Используем SIL CurrentUserHelper, чтобы проверить, что текущий пользователь администратор Jira */

        if (currentUserHelper.isUserAdministrator()) {

/*  Используем SIL сервисы для получения тикетов по JQL запросу */

            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");

    }

Я сделал комментарии по коду.

Сервис KIssueFieldsService


Хотел бы отдельно рассказать про этот сервис. В SIL во все методы, где требуется передать кастомное поле, мы можем передавать либо имя поля, либо идентификатор поля в формате customfield_NNNNN, либо алиас поля.

Если бы мы не использовали сервисы SIL, то нам пришлось бы реализовывать эту логику самостоятельно. Но используя сервис KIssueFieldsService мы задействуем эту функциональность одной строчкой:

CustomField customField= this.kIssueFieldsService.getCustomField(customFieldName);

Теперь Вы знаете, как создавать переносимы между версиями Jira функции SIL.