Пишем плагин для Atlassian Jira

в 8:17, , рубрики: java, Песочница, метки:

Atlassian Jira — довольно популярный багтрекер/система управления проектами. Одним из ее плюсов является то, что она хорошо поддается разного рода доработкам.

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

Есть несколько других хороших трекеров вроде Redmine и YouTrack, но сравнивать я их не стану.
Предположим, систему вы уже выбрали и/или она уже используется, и у вас есть программист, знающий Java.

Я буду описывать весь процесс для Jira начиная с версии 5. Для четвертой все сложнее, для третьей разработка плагина больше похожа на ад.

Также для примеров я буду использовать IDE Intellij IDEA CE. Все описанное можно адаптировать и для других IDE.

Типы модулей

Atlassian предоставляет api для написания разнообразных модулей. Их можно писать и к другим ихним продуктам, вроде Confluence, мы же сосредоточимся на Jira.
Модули представляют собой чаще всего разного рода виджеты, кнопочки и страницы, при взаимодействии с которыми выполняется ваш код. Они имеют фиксированное место в системе и по-умолчанию изолированы друг от друга.
С помощью модуля, например, можно добавить дополнительный таб в окно просмотра задачи с выводом какой-либо дополнительной информации, при желании интерактивный, добавить кнопочку в меню действий над задачей, или же заставить выполняться свой код во время одного из переходов по workflow.

Уровень выше — настраиваемый интерактивный виджет для дашборда, который общается с системой через RESTful сервис.
Или, например, собственная обработка почтовых сообщений.
Один плагин может содержать сколько угодно модулей, как правило, объединенных общей идеей и назначением.

Программное взаимодействие с системой

У Jira есть довольно богатый api. Он позволяет делать всякие вещи с задачами, проектами и пользователями без ручного обращения к бд и прочего безобразия. Реализуется он через компоненты с названиями вроде UserManager, IssueManager, etc, попадающие в класс вашей реализации модуля с помощью dependency injection во время первой загрузки плагина (иногда во время первого обращения к коду). Удобней всего засовывать их через конструктор, таким образом, ваш код может выглядеть вот так:

public class ConfigureGradesAction extends JiraWebActionSupport {

    private final JiraAuthenticationContext authenticationContext;
    private final WebResourceManager webResourceManager;
    private final ActiveObjects ao;
    private final VelocityManager velocityManager;
    private final ProjectRoleManager projectRoleManager;

    ConfigureGradesAction(
        JiraAuthenticationContext authenticationContext,
        WebResourceManager webResourceManager,
        ActiveObjects ao,
        VelocityManager velocityManager,
        ProjectRoleManager projectRoleManager
    ) {
        this.authenticationContext = authenticationContext;
        this.webResourceManager = webResourceManager;
        this.ao = ao;
        this.velocityManager = velocityManager;
        this.projectRoleManager = projectRoleManager;
    }
// ...
}

Довольно монструозно, а что поделать. В Intellij IDEA есть удобное автозаполнение конструктора парой нажатий. Может быть, в eclipse и netbeans тоже.

Все эти менеджеры можно подгружать и в рантайме через статические методы класса ComponentAccessor. В нем не представлены все api, которые возможно заинжектить, но большинство основных там есть. Классный хинт: набираешь ComponentAccessor, точку, и получаешь 80% api джиры. Это очень удобно, особенно в первое время.

Давайте уже что-нибудь напишем

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

Настройка среды разработки

Для начала необходимо установить и настроить atlassian sdk. Это — большой шаг.
Он очень облегчает работу. Представляет собой набор maven-целей в собственных обертках с собственным окружением.
Процесс установки хорошо описан в официальной документации. Вообще, она покрывает, наверное, 90% всего, что можно сделать с Jira.
Сейчас у них появились установочные пакеты под MacOS, Debian/Ubuntu, CentOS/Fedora и Windows. Раньше не было.
Поэтому установка сводится к двум шагам:
1) Установка jdk
2) Установка atlassian sdk из готового пакета

Если вашей системы нет в списке, значит вы достаточно крутой, чтобы разобраться самостоятельно.
Теперь у вас должен появиться набор консольных утилит, начинающихся с atlas-.
Чтобы создать скелет для плагина, нужно набрать atlas-create-jira-plugin и ответить на пару вопросов.
По окончании в папке с именем, указанном в artifactId, создается maven-артифакт вашего плагина.

  • Версия — Конечно же, Shiny new JIRA 5
  • groupId — Можно относиться как к неймспейсу для ваших плагинов. Для вашего домена, например, com.mycompany.jira.plugins
  • artifactId — Уникальное название плагина. Например, reverse-jira-issue
  • version — Я обычно оставяю дефолтное значение
  • package — Это java package, в неймспейсе которого будет ваш код. По-умолчанию предлагается то же, что было введено в groupId, но я предпочитаю писать в формате groupId.artifactId.
    На имя com.mycompany.jira.plugins.reverse-jira-issue будет ругаться компилятор, поэтому в нашем случае напишем com.mycompany.jira.plugins.issue.reverse
Добавление модуля

В atlassian sdk есть команда, создающая scaffold для модулей в плагине — atlas-create-jira-plugin-module. Почему бы не использовать ее. Она спросит тип модуля — выбираем Web Item
Имя — Reverse Issue Web Item; Section — operations-top-level (ссылки на все типы секшенов внизу этой страницы)
Спросит так же ссылку, которая должна будет открываться по кнопке. Наберем туда путь к нашему будущему действию — /secure/ReverseIssueAction.jspa
После всех ответов скрипт добавит в src/main/resources/atlassian-plugin.xml следующую запись:

<web-item name="Reverse Issue Web Item" i18n-name-key="reverse-issue-web-item.name" key="reverse-issue-web-item" section="operations-top-level" weight="1000">
    <description key="reverse-issue-web-item.description">The Reverse Issue Web Item Plugin</description>
    <label key="reverse-issue-web-item.label"></label>
    <link linkId="reverse-issue-web-item-link">/secure/ReverseIssueAction.jspa</link>
</web-item>

Никаких java-классов не создастся, данный модуль обходится без кода.
Попробуем запустить и посмотреть, что получилось. Делается это командой atlas-run.
Она собирает инстанс джиры в подпапке target/jira и вытаскивает его на адрес 127.0.0.1:2990/jira/.
Первоначальная сборка и загрузка всех зависимостей займет длительное время.
Теперь заходим на указанный адрес и в логин-пароль вбиваем admin/admin. Создаем проект с произвольным названием через administration -> Create your first project, после чего выходим на главную страницу,
кликает Create Issue, пишем какое-нибудь название и переходим в созданную задачу.
В действиях над задачей появится кнопка Reverse Issue Web Item. Пока что она ведет на несуществующий линк. Исправим это, добавив свой action.

Создание действия

Action — это код, выполняющийся при обращении к url вида /secure/ActionName!doSomething.jspa
Джира здесь использует свой фреймворк Webwork, который при обращении по url создает объект вашего класса, назначает объявленным полям объекта переданные в post и get параметры, дергает в нем метод, в примере выше public string doSomething(), а без doSomething в конце имени — public string doDefault(), и дальше либо редиректит куда-то, либо передает этот инстанс в шаблон velocity, который вернет ответ пользователю.
Для простоты мы обойдемся без шаблона, а сделаем редирект обратно в задачу.
Сначала воспользуемся scaffolding'ом для создания экшена.
atlas-create-jira-plugin-module -> Webwork Plugin -> Reverse Issue Webwork Module
name: Reverse Issue
В atlassian-plugin.xml добавится нечто подобное:

  <webwork1 key="reverse-issue" name="Reverse Issue" i18n-name-key="reverse-issue.name">
    <description key="reverse-issue.description">The Reverse Issue Plugin</description>
    <actions>
      <action name="com.mycompany.jira.plugins.jira.webwork.ReverseIssueAction" alias="ReverseIssueAction">
        <view name="success">/templates/reverse-issue/success.vm</view>
        <view name="input">/templates/reverse-issue/input.vm</view>
        <view name="error">/templates/reverse-issue/error.vm</view>
      </action>
    </actions>
  </webwork1>

Здесь action alias должен по имени совпадать с link'ом в web-item, например
ReverseIssueAction и /secure/ReverseIssueAction.jspa
Если не совпадает, то или другое нужно поправить.

Перезагрузка кода

Теперь можно применить сделанные изменения на локальном инстансе.
Удобнее всего сделать это не перезагружая сам инстанс с помощью тулзы atlas-cli.
В соседней консоли откроем папку плагина и напишем этот самый atlas-cli.
После подрузки введем команду pi — сокращенно от plugin install.
Теперь по нажатию нашей созданной в самом начале кнопки будет открываться страница с дефолтным шаблоном success.vm.

Редирект

Нам же нужен редирект обратно, для чего, для начала, следует передать в get-е ссылку на задачу.
Для этого поправим link в atlassian-plugin.xml на /secure/ReverseIssueAction.jspa?issue=$issue.id
На самом деле, эта запись прогоняется через velocity как шаблон, и в умолчальном контексте здесь есть объект $issue, на котором можно вызывать всякие методы. Это описано где-то в дебрях документации.
Снова pi в нашем atlas-cli, чтобы проверить, что параметр issue действительно передается.
Ссылка по кнопке в этот раз откроется такая: 127.0.0.1:2990/jira/secure/ReverseIssueAction.jspa?issue=10000
Чтобы экшен подцепил параметр, нужно объявить в нем сеттер setIssue(long issue) и одноименное(а можно и не очень) свойство issue.

public class ReverseIssueAction extends JiraWebActionSupport
{
    private static final Logger log = LoggerFactory.getLogger(ReverseIssueAction.class);
	
	private long issue;

    @Override
    public String execute() throws Exception {
        log.warn(Long.toString(issue));
        return super.execute(); //returns SUCCESS
    }

    public void setIssue(long issue) {
        this.issue = issue;
    }
}

Добавим так же в метод execute строчку log.warn(Long.toString(issue));
Она распечатает id задачи в консоль, в которой запущен сервер.
Снова делаем pi и проверяем, что все работает. Должно работать, почему бы и нет.
Чтобы сделать редирект, нам недостаточно id задачи, нужен ее ключ. На самом деле, ключ мы могли передать на первом шаге, но я хочу показать, как базово использовать апи джиры. Объявим компонент IssueManager в конструкторе и достанем из него запрос по id, из которого достанем ключ.
Сразу же сделаем редирект.

public class ReverseIssueAction extends JiraWebActionSupport
{
    private static final Logger log = LoggerFactory.getLogger(ReverseIssueAction.class);
	
	private long issue;

    private final IssueManager issueManager;

    public ReverseIssueAction(IssueManager issueManager) {
        this.issueManager = issueManager;
    }

    @Override
    public String execute() throws Exception {
        log.warn(Long.toString(issue));
        String key = issueManager.getIssueObject(issue).getKey();
        return getRedirect("/browse/"+key);
    }

    public void setIssue(long issue) {
        this.issue = issue;
    }
}
Основной функционал

Теперь осталось перевернуть текст в задаче.

public class ReverseIssueAction extends JiraWebActionSupport
{
    private static final Logger log = LoggerFactory.getLogger(ReverseIssueAction.class);
	
	private long issue;

    private final IssueManager issueManager;

    public ReverseIssueAction(IssueManager issueManager) {
        this.issueManager = issueManager;
    }

    @Override
    public String execute() throws Exception {
        log.warn(Long.toString(issue));
        MutableIssue issueObject = issueManager.getIssueObject(issue);
        String key = issueObject.getKey();
        String newSummary = reverse(issueObject.getSummary());
        issueObject.setSummary(newSummary);
        String newDescription = reverse(issueObject.getDescription());
        issueObject.setDescription(newDescription);
        boolean sendNotification = true;
        issueManager.updateIssue(getLoggedInUser(), issueObject, EventDispatchOption.ISSUE_UPDATED, sendNotification);
        return getRedirect("/browse/"+key);
    }

    private static String reverse(String s) {
        return new StringBuilder(s).reverse().toString();
    }

    public void setIssue(long issue) {
        this.issue = issue;
    }    

}

Снова делаем pi в консоли утилиты atlas-cli и проверяем кнопочку.

Автор: Firfi

Источник


  1. Юрий:

    Огромное Вам спасибо за статью, очень помогла во многом разобраться

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js