Всем привет!

В этой статье я хочу рассказать, как создать свою функцию в SIL.

Вступление


SIL расшифровывается, как Simple Issue Language, был создан разработчиками компании cPrime для автоматизации ручных действий в Atlassian Jira и Confluence.

Основное преимущество SIL заключается в том, что SIL содержит функции, которые не требуют знания API Atlassian Jira или Atlassian Confluence для их использования. Это значительно снижает порог вхождения в SIL и делает программный код на SIL меньше по объему, нежели аналогичный код на Java или Groovy.

Например, вы хотите выбрать тикеты из Jira по запросу, чтобы потом произвести над ними какие-то действия. Для этого на Java или Groovy Вам бы пришлось написать с десяток строк кода. На SIL это одна строка:

selectIssues("Ваш JQL запрос");

Более того, на Java или Groovy Вам пришлось бы обеспечить совместимость кода на Jira 7 и Jira 8. В SIL Вам думать о совместимости не нужно. SelectIssues будет работать на всех версиях Jira, которые поддерживает SIL.

Вы можете найти больше про SIL здесь.
А что делать, если мне нужно сделать что-то такое, для чего функций в SIL нет? Например, мне нужно получить данные из плагина Table Grid Next Generation или сформировать PDF файл с помощью плагина PDF exporter for Jira.

В этом случае Вам доступны две опции:

  1. SIL Groovy Connector — этот плагин позволяет написать код с иcпользованием Jira Java API на Java или Groovy и вызывать этот код из SIL скрипта.
  2. Написать свой Jira плагин, который добавит Ваши функции в SIL, и потом использовать Ваши функции в SIL скриптах

В этой статье мы остановимся на том, как написать свой Jira плагин, чтобы расширить функции SIL. Для того, чтобы повторить действия из этой статьи Вам понадобятся Atlassian SDK и git.

Устанавливаем maven архетип


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

Сначала склонируем архетип из репозитория:

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

Будет создана папка sil-extenstion-archetype. Перейдем в нее:

cd sil-extension-archetype

Теперь установим архетип в Ваш локальный репозиторий maven:

atlas-mvn install

Создаем плагин


Переходим в папку выше и создаем новый плагин

cd ..
atlas-mvn archetype:generate -DarchetypeCatalog=local

Вам будут заданы стандартные вопросы при создании проекта через maven. Привожу свои ответы на вопросы. Вы можете их изменить.

Choose archetype:

1: local -> com.cprime.jira.sil.extension:sil-extension-archetype (This is the com.cprime.jira.sil.extension:sil-extension plugin for Atlassian JIRA.)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): : 

Define value for property 'groupId': ru.matveev.alexey.sil.extension
Define value for property 'artifactId': sil-extension
Define value for property 'version' 1.0-SNAPSHOT: : 
Define value for property 'package' ru.matveev.alexey.sil.extension: : 

Confirm properties configuration:
groupId: ru.matveev.alexey.sil.extension
artifactId: sil-extension
version: 1.0-SNAPSHOT
package: ru.matveev.alexey.sil.extension

 Y: : Y

После этого плагин будет создан.

Тестируем плагин


Переходим в созданную папку( в моем случае это sil-extension) и запускаем Jira:

cd sil-extension
atlas-run</code>
Открываем Jira в Вашем браузере
<code>http://localhost:2990/jira/plugins/servlet/silmanager?autoSelectTree=sil.scripts

Вы увидите экран SIL Manager. Выбираем опцию New File:



Создайте файл test.sil следующего содержания:

runnerLog(SayHello("Alexey"));
string[] res = SayHello2("Alexey", "Matveev");
runnerLog(res[0]);
runnerLog(res[1]);
res = SayHello3("Alexey", "Matveev");
runnerLog(res["param1"]);
runnerLog(res["param2"]);

Экран в Jira будет выглядеть примерно вот так:



Наш плагин добавил три функции: SayHello, SayHello2, SayHello3. Test.sil проверяет, что функции добавились в SIL.

Запустим test.sil, нажав на кнопку Run. Во вкладке Console Вы должы увидеть следующий текст:

Hello Alexey
Hello Alexey
Hello Matveev
Hello Alexey
Hello Matveev
Done.

Если Вы увидели такой текст, то наши функции успешно добавились в SIL.

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

Создаем Java класс для нашей функции


Каждая функция, которую Вы хотите добавить в SIL, должна иметь свой класс. Этот класс должен быть наследником класса AbstractSILRoutine.

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

SayHello.java


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

  public SayHello(ClassLoader classLoader, String name) { 
    super(classLoader, name, types);
  }

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

  @Override 
  protected SILValue<MutableString> runRoutine(SILContext silContext,                                   List<SILValue<?>> list) { 
    SILValue param = list.get(0); 
    return SILValueFactory.string( "Hello " + param.toStringValue());
 }
  @Override 
  public String getParams() { return "(name)"; }

Первая строка:

private static final SILType[][] types = {{ TypeInst.STRING }};

Эта строка определяет типы и количество параметров, которые будут переданы в Вашу функцию. Функция в SIL выглядет вот так:

mymethod(myparameter);

Соответсвенно {{ TypeInst.STRING }} говорит о том, что у нас будет один параметр и его тип будет String. Если Вы хотите передать два параметра типа String, то нужно изменить строку вот так:

private static final SILType[][] types = {{ TypeInst.STRING, TypeInst.STRING }};

В SIL Вы можете вызвать Вашу функцию вот так:

mymethod(myparameter1, myparameter2);

Если Ваша функция может принимать как один, так и два параметра. То срока будет выглядеть вот так:

private static final SILType[][] types = {{ TypeInst.STRING},  
                                                          {TypeInst.STRING, TypeInst.STRING }};

Далее идет:

public class SayHello extends AbstractSILRoutine<MutableString>

Мы наследуем наш класс от AbstractSILRoutine, что означает что наш класс будет возвращать параметр типа String.

Далее:

public SayHello(ClassLoader classLoader, String name) { 
  super(classLoader, name, types);
}

Это конструктор. Вам ничего в нем менять, скорее всего, не придется.

Далее:

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

Здесь мы определяем тип возвращаемого параметра. В нашем случае это String.

Далее:

@Override 
protected SILValue<MutableString> runRoutine(SILContext silContext, List<SILValue<?>> list) { 
SILValue param = list.get(0); 
return SILValueFactory.string( "Hello " + param.toStringValue());
}

Это основная точка входа для Вашей функции. При выполнении функции в SIL, Вы попадете сюда. Вся логика находится здесь.

Эта функция принимает два параметра:

silContext — позволяет достать переменные Jira и SIL.

Например, Ваш SIL скрипт работает ка пост функция и Вы хотите получить тикет, под которым выполняется скрипт:

Issue issue = (Issue) silContext.getAllMetaInformation().get("issue");

list — позволяет получить значения параметров, переданных в функцию SIL.

SILValue param = list.get(0); 

Строка:

SILValueFactory.string( "Hello " + param.toStringValue());

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

Далее:

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

Здесь определяется вид функции в SIL Manager. Когда Вы начинаете печатать в SIL manager, то Вы виде подсказки доступных функций. В нашем случае Вы увидите:

yourmethod(name)

Добавим наш класс в SIL engine


Вы создали свой класс. Теперь нужно сделать так, чтобы SIL знал о Вашей функции. Это делается в файле ESLauncher.java. В этом файле есть вот такие строчки:

RoutineRegistry.register(new SayHello( classLoader,"SayHello")); 

Эта строка говорит о том, что мы добавляем класс SayHello в SIL под именем SayHello. Имя класса и функции могут не совпадать.

Далее нужно добавить вот такую строку:

RoutineRegistry.unregister("SayHello");

Измените SayHello на имя Вашей функции. Эта строчка выгрузит Вашу функцию из SIL, если вы удалите Ваш плагин из Jira.

Больше о SIL


В классе SayHello2, мы возвращаем List.

SILValue param1 = list.get(0); 
SILValue param2 = list.get(1); 
List<String> res = new ArrayList<>(); 
res.add("Hello " + param1.toStringValue()); 
res.add("Hello " + param2.toStringValue()); 
return SILValueFactory.stringArray(res);

Для того, чтобы это заработало, нужно еще изменить возвращаемое значение на TypeInst.STRING_ARR and унаследоваться от AbstractSILRoutine with KeyableArraySILObject.

В SIL Вы вызываете SayHello2 вот так:

string[] res = SayHello2("Alexey", "Matveev");
runnerLog(res[0]);
runnerLog(res[1]);

В SayHello3 мы возвращаем Map:

SILValue param1 = list.get(0); 
SILValue param2 = list.get(1); 
Map<String, String> res = new HashMap<>(); 
res.put("param1","Hello " + param1.toStringValue()); 
res.put("param2","Hello " + param2.toStringValue()); 
return SILValueFactory.stringArray(res);

В SIL можно вызвать SayHello3 вот так:

res = SayHello3("Alexey", "Matveev");
runnerLog(res["param1"]);
runnerLog(res["param2"]);

В этом случае мы получаем элементы массива не по индексу, а по ключам.

Если Вы посмотрите на код в Java, то мы в обоих случаях работаем с KeyableArraySILObject. Дело в том, что в SIL List и Map реализуются классом KeyableArraySILObject. Т.е. к любому массиву в SIL, можно обращаться как по индексу, так и по ключу.

Передаем массив в качестве параметра


Мы умеем возвращать из функции SIL массив. Теперь давайте посмотрим, как передать массив в качестве параметра.

Код будет выглядеть вот так:

@Override
protected SILValue<MutableString> runRoutine(SILContext silContext, List<SILValue<?>> list) {
    GenericArraySILObject rowsToUpdate =  (GenericArraySILObject) list.get(0).getObject();
    Map<String, int[]> keys = rowsToUpdate.getKeysMapping()
......
}

Сначала мы получаем из параметра объект класса GenericArraySILObject, а потом получаем из него Map.

В SIL можно передавать массив вот так:

string[] arr;
arr["key1"] = "value1";
arr["key2"] = "value2";
yourmethod(arr);

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