- PVSM.RU - https://www.pvsm.ru -
В крупных организациях часто возникает необходимость прикрутить к JIRA какой-либо дополнительный функционал, которого нет в стандартной поставке: автоматизацию, интеграцию с другими системами и прочие кастомизации. Зачастую это решается сторонними плагинами, в Atlassian Market их огромное количество. Но что делать, если подходящего плагина нет? Или он стоит 3000$, а вам нужна всего одна функция в нем? Очевидно, написать свой. Ещё один вариант для расширения — плагины, добавляющие возможность использовать свои скрипты в JIRA: ScriptRunner [1](Groovy), Jira Scripting Suite [2] (SIL), JJupin [3] (Jython).
В этой статье я расскажу о самом популярном и функциональном из них — ScriptRunner [4] от Adaptavist [5].
ScriptRunner позволяет писать свои скрипты на Groovy, которые могут напрямую использовать JIRA Java API. Т.е. вам доступен практически такой же функционал, как и при создании своих плагинов. Только писать расширения на Groovy гораздо приятнее: никакой возни с xml-конфигами, maven-боли и проблем с зависимостями, только код. У JIRA довольно богатый и хорошо документированный Java API. В отличии от Rest API, на Java API можно сделать всё, что можно сделать через веб-интерфейс и даже больше. Groovy [6] — это скриптовый язык с Java-подобным синтаксисом. Он компилируется в Java байткод и выполняется JVM, работает с Java библиотеками, как с родными, поддерживает одновременно статическую и динамическую типизацию и замыкания [7].
Начиная с третьей версии плагина все скрипты должны находиться в одной определённой директории (%scriptroot%). По умолчанию это директория <jira-app-dir>/scripts. Изменить её можно, отредактировав параметр -Dplugin.script.roots в jvm args, вот так:
-Dplugin.script.roots="/home/jira/jira-data/scripts/"
Подробнее о том, как добавить или изменить параметры в jvm args можно прочитать здесь [8].
В ScriptRunner есть несколько основных типов кастомных скриптов, различающихся по назначению и применению:
PostFunction. Выполняется после перевода issue в другой статус. Привязывается к конкретному переходу.
Validator. Валидация данных при попытке перевести issue в другой статус. Разрешает или запрещает перевод статуса. Привязывается к конкретному переходу.
Conditions. Логическое выражение, определяющее, можно ли сделать перевод статуса. Если нельзя — скрывает кнопку для изменения статуса. Привязывается к конкретному переходу.
Listener. Реагирует на события IssueEvent. Например, на создание или обновление issue. Можно указать, на какие события реагировать и в каких проектах.
JQL Functions. JQL-функции для поиска issue.
Любой скрипт можно выполнить прямо в окне браузера, через админ-панель, что очень удобно для тестирования небольших скриптов. Давайте уже напишем первый скрипт и запустим его. Установите плагин на свой тестовый инстанс JIRA, если вы этого ещё не сделали. Зайдите в Administration > Add-ons > ScriptRunner > Script Console. Как видите, кнопка Run недвусмысленно намекает, что код в консоли можно запустить прямо отсюда. Хвала богам, в третьей версии окно консоли получило подсветку синтаксиса и ошибиться стало чуть сложнее! Обратите внимание на закладки File / Script справа: можно выполнить скрипт, указав к нему путь относительно %scriptroot%. Вставьте в окно вот такой код, заменив ключ issue на реально существующий в вашем инстансе:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
IssueManager issueManager = ComponentAccessor.getIssueManager()
// Замените PRJ-1 на ключ существующего issue.
MutableIssue curIssue = issueManager.getIssueObject("PRJ-1")
String result = curIssue.key + ": " + curIssue.summary
В результате работы скрипта выводится содержимое последней переменной в скрипте, в данном случае это result.
Этот раздел можно пропустить, если вы собираетесь писать только совсем небольшие скрипты. В других случаях, конечно, удобнее будет вести разработку в IDE с автодополнением кода, дебаг-режимом, системой контроля версий и прочими плюшками. В качестве IDE нам подойдет IntelliJ IDEA, она поддерживает Groovy прямо из коробки. Итак, нам нужно установить IntelliJ IDEA [9] (подойдет Community Edition), Java SDK [10] и Groovy [11]. Установите IDEA и создайте новый проект (File > New Project). Выберите Groovy проект, укажите путь к JDK и к Groovy.
Теперь нам нужно подключить библиотеки JIRA, чтобы было доступно автодополнение кода. Самый простой вариант — скопировать к себе папки classes и lib из <jira-app-dir>/atlassian-jira/WEB-INF. Самый лучший вариант — скачать исходники с официального сайта. Откройте настройки проекта: File > Project Structure > Project Settings > Libraries. Добавьте папки classes и lib к проекту:
Теперь давайте настроим удаленную отладку. Чтобы включить дебаг режим в JIRA, нужно к jvm args добавить параметр:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
После этого откройте настройку конфигураций Run > Edit Configurations, добавьте новый Remote Config. На вкладке Configuration введите имя конфигурации, например, «Jira Debug» и укажите хост, на котором находится ваша JIRA. Естественно, номер порта для отладки должен быть таким же, каким вы указали его в параметре address в jvm args.
Проверяем. Создайте скрипт в корне вашего проекта. Скопируйте этот скрипт на JIRA-инстанс, в папку %scriptroot%. Запустите отладку: нажмите Run > Debug 'Jira Debug'. Вы увидите сообщение: Connected to the target VM, address: %your_host%, transport: 'socket'. В Script Console введите имя вашего скрипта и нажмите Run. Вы должны увидеть примерно такую картину:
Имейте ввиду, что JIRA не будет отвечать, пока вы не отпустите отладку, т.е. вам точно нужен отдельный инстанс для дебага. Кстати, в IDEA для проверки Groovy-кода можно использовать Groovy Console (Tools > Groovy Console).
Для работы с сущностями в JIRA API используются компоненты с соответствующими именами, вот некоторые из них: IssueManager, ProjectManager, UserManager, SearchService, CustomFieldManager, CommentManager. Их экземпляры можно получить через ComponentAccessor, ниже вы увидите это в примерах. В поисках нужного метода вам поможет IDEA с автодополнением кода, а с описанием — документация JIRA Java API [12]. Узнать ID какой-либо сущности можно через интерфейс самой JIRA, просто найдите ссылки для её редактирования или просмотра, в большинстве случаев в url будет содержаться нужный вам ID. Что примечательно, в сети очень много примеров работы с JIRA на Java и практически всегда этот код будет работать на Groovy без изменений.
Пожалуй, самой распространенной задачей в скриптах будет изменение issue. Давайте напишем код, который меняет summary и какое-нибудь кастомное поле.
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
// Получаем компоненты-менеджеры
ComponentManager componentManager = ComponentManager.getInstance()
IssueManager issueManager = ComponentAccessor.getIssueManager()
CustomFieldManager customFieldManager = componentManager.getCustomFieldManager()
// Текущий пользователь
User curUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
// Получаем issue по его ключу
MutableIssue curIssue = issueManager.getIssueObject("PRJ-1")
// 1. Обновляем значение системного поля Summary.
// Присваиваем новое значение поля в объекте issue.
curIssue.summary = "New summary"
// Записываем изменения issue в Jira.
// Обратите внимание, что при этом будет вызван event IssueUpdated, на который среагируют лиссенеры, если таковые имеются.
// Также произойдет реиндекс обновленного issue, обновится история issue, и новое значение поля будет сразу видно в интерфейсе.
issueManager.updateIssue(curUser, curIssue, EventDispatchOption.ISSUE_UPDATED, false)
// 2. Обновляем значение кастомного поля. (1)
// Получаем кастомное поле по имени.
CustomField cfReleaseVersion = customFieldManager.getCustomFieldObjectByName("Release Version")
// Обновление значения с записью в историю и вызовом event
curIssue.setCustomFieldValue(cfReleaseVersion, "3.5")
issueManager.updateIssue(curUser, curIssue, EventDispatchOption.ISSUE_UPDATED, false)
// 3. Обновляем значение кастомного поля. (2)
// Получаем кастомное поле по id.
CustomField cfUpdateVersion = customFieldManager.getCustomFieldObject("customfield_13500")
// "Тихое" обновление кастомного поля, оно не вызовет event и не будет отражено в истории issue.
cfUpdateVersion.updateValue(null, curIssue, new ModifiedValue(null, "2.4"), new DefaultIssueChangeHolder())
Далее парочка примеров с комментариями.
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.bc.issue.search.SearchService
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.web.bean.PagerFilter
IssueManager issueManager = ComponentAccessor.getIssueManager()
SearchService searchService = ComponentAccessor.getComponent(SearchService.class)
User curUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
// JQL-запрос для поиска issue, созданных за последние два дня
String JqlQuery = "created > -2d"
def parseResult = searchService.parseQuery(curUser, JqlQuery)
def searchResult = searchService.search(curUser, parseResult.getQuery(), PagerFilter.getUnlimitedFilter())
def IssuesByJql = searchResult.issues.collect { issueManager.getIssueObject(it.id) }
return IssuesByJql
import com.atlassian.crowd.embedded.api.User
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.link.IssueLinkManager
import com.atlassian.jira.issue.link.IssueLinkType
import com.atlassian.jira.issue.link.IssueLinkTypeManager
IssueManager issueManager = ComponentAccessor.getIssueManager()
IssueLinkManager issueLinkManager = ComponentManager.getInstance().getIssueLinkManager()
IssueLinkTypeManager issueLinkTypeManager = (IssueLinkTypeManager) ComponentManager.getComponentInstanceOfType(IssueLinkTypeManager.class)
User curUser = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
// Получаем тип линка по имени
IssueLinkType duplicateLinkType = issueLinkTypeManager.getIssueLinkTypesByName("Duplicate")?.first()
if (duplicateLinkType != null) {
def issue_1 = issueManager.getIssueObject("PRJ-1")
def issue_2 = issueManager.getIssueObject("PRJ-2")
// Создаем линк PRJ-1 duplicates PRJ-2
issueLinkManager.createIssueLink(issue_1.id, issue_2.id, duplicateLinkType.id, null, curUser)
}
В WorkFlow-скриптах (PostFunction, Validator, Condition) доступ к текущему issue можно получить через переменную issue, она биндится ScriptRunner-ом. Лиссенеры организованы чуть по-другому. Вам нужно создать свой класс, наследующий AbstractIssueEventListener.
Простейший лиссенер выглядит вот так:
package Listeners // лиссенер должен находиться в папке %scriptroot%/Listeners
import com.atlassian.jira.event.issue.AbstractIssueEventListener
import com.atlassian.jira.event.issue.IssueEvent
import com.atlassian.jira.issue.Issue
import org.apache.log4j.Level
import org.apache.log4j.Logger
class SimpleListener extends AbstractIssueEventListener {
Logger log = Logger.getLogger(this.class.simpleName)
@Override
void workflowEvent(IssueEvent event) {
this.customEvent(event)
}
@Override
void customEvent(IssueEvent event) {
Issue curIssue = event.issue
log.setLevel(Level.DEBUG)
log.debug("Event catch: ${event.eventTypeId} fired for ${curIssue.key} (${curIssue.issueTypeObject.name}).")
}
}
На Groovy вы можете выполнять http-запросы, читать данные из БД, отправлять письма, использовать JIRA Java API и стандартные Java библиотеки. В самом ScriptRunner еще много возможностей, о которых я не упомянул в статье: scripted fields, юнит-тесты, встроенные скрипты и многое другое. К сожалению, начиная с 4-й версии, плагин стал платным. Если вы планируете обновлять JIRA до 7 версии (или уже её используете), учтите, что на неё можно поставить только четвертую платную версию ScriptRunner. Но все основные функции доступны во 2-й и 3-й бесплатных версиях. Приведённые выше скрипты проверялись на JIRA 6.4.12 (Java 8) и ScriptRunner 3.0.16.
Официальный сайт ScriptRunner [4]
ScriptRunner на Atlassian Marketplace [13]
Официальный сайт Groovy [6]
Документация JIRA API [12]
Статья Rocking With Jira ScriptRunner [14] (осторожно, местами устаревший код)
Старая документация ScriptRunner (для версий до 3.0) [15]
Автор: Петер-Сервис
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/109310
Ссылки в тексте:
[1] ScriptRunner : https://marketplace.atlassian.com/plugins/com.onresolve.jira.groovy.groovyrunner
[2] Jira Scripting Suite: https://marketplace.atlassian.com/plugins/com.quisapps.jira.jss
[3] JJupin: https://marketplace.atlassian.com/plugins/com.keplerrominfo.jira.plugins.jjupin
[4] ScriptRunner: https://scriptrunner.adaptavist.com
[5] Adaptavist: http://www.adaptavist.com
[6] Groovy: http://www.groovy-lang.org
[7] замыкания: http://www.groovy-lang.org/closures.html
[8] здесь: https://confluence.atlassian.com/jira/setting-properties-and-options-on-startup-120007.html
[9] IntelliJ IDEA: https://www.jetbrains.com/idea/download
[10] Java SDK: http://www.oracle.com/technetwork/java/javase/downloads/index.html
[11] Groovy: http://www.groovy-lang.org/download.html
[12] документация JIRA Java API: https://developer.atlassian.com/jiradev/jira-apis
[13] ScriptRunner на Atlassian Marketplace: https://marketplace.atlassian.com/plugins/com.onresolve.jira.groovy.groovyrunner/server/overview
[14] Статья Rocking With Jira ScriptRunner: http://igorpopov.io/2014/11/24/rocking-with-jira-script-runner/
[15] Старая документация ScriptRunner (для версий до 3.0): https://jamieechlin.atlassian.net/wiki/display/GRV/Script+Runner
[16] Источник: http://habrahabr.ru/post/271805/
Нажмите здесь для печати.