- PVSM.RU - https://www.pvsm.ru -

Автоматизация Jira на Groovy

image

В крупных организациях часто возникает необходимость прикрутить к 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].

ScriptRunner

Начиная с третьей версии плагина все скрипты должны находиться в одной определённой директории (%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.
Автоматизация Jira на Groovy - 2

Настройка рабочего окружения

Этот раздел можно пропустить, если вы собираетесь писать только совсем небольшие скрипты. В других случаях, конечно, удобнее будет вести разработку в IDE с автодополнением кода, дебаг-режимом, системой контроля версий и прочими плюшками. В качестве IDE нам подойдет IntelliJ IDEA, она поддерживает Groovy прямо из коробки. Итак, нам нужно установить IntelliJ IDEA [9] (подойдет Community Edition), Java SDK [10] и Groovy [11]. Установите IDEA и создайте новый проект (File > New Project). Выберите Groovy проект, укажите путь к JDK и к Groovy.

New Project

Автоматизация Jira на Groovy - 3

Теперь нам нужно подключить библиотеки JIRA, чтобы было доступно автодополнение кода. Самый простой вариант — скопировать к себе папки classes и lib из <jira-app-dir>/atlassian-jira/WEB-INF. Самый лучший вариант — скачать исходники с официального сайта. Откройте настройки проекта: File > Project Structure > Project Settings > Libraries. Добавьте папки classes и lib к проекту:

Project Libraries

Автоматизация Jira на Groovy - 4

Теперь давайте настроим удаленную отладку. Чтобы включить дебаг режим в 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. Вы должны увидеть примерно такую картину:

Groovy Debugging

Автоматизация Jira на Groovy - 5

Имейте ввиду, что 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())

Далее парочка примеров с комментариями.

Поиск issue по JQL запросу

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

Создание линка между issue

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/