company_banner

Кастомизация JIRA и Confluence с помощью плагинов

    Практически все организации, где необходимо вести документацию и управлять бизнес-процессами, сталкиваются с необходимостью выбора и настройки софта, который облегчает эти процессы. JIRA и Confluence от компании Atlassian Software достаточно популярны на российском рынке. Однако в своей дефолтной конфигурации они не могут решить все задачи, стоящие перед организацией – ведь у каждого своё видение процессов работы. Кроме того требования кастомизировать JIRA растут по мере использования продукта.

    Я работаю разработчиком JIRA/Confluence в Mail.Ru Group, и хочу поделиться опытом написания плагинов JIRA и Confluence: что можно сделать, как сделать и о чём нужно не забыть.

    О чем я расскажу

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

    Описание плагинов будет включать:
    • пользовательские поля (custom fields), обработчики событий (listeners);
    • сервлеты (servlets) и REST, программирование workflow.

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

    Пользовательские поля

    Итак, начнём с пользовательских полей. Поля являются базовой единицей данных в JIRA. Они содержат данные требований (issues). Поля в JIRA в целом можно разделить на две основные категории: системные поля и пользовательские поля. К системным полям относятся различные формы, такие как текстовые поля, раскрывающиеся списки и другие. На практике базовой функциональности часто не хватает, и нам требуется программировать свои пользовательские поля (например, поле вычисляющее сумму других полей или количество комментариев).

    Первый плагин (Query Issues Custom Fields) – это список, значения которого – ключи требований (issues), которые выбираются из настроенного запроса поиска (JQL). Значение списка – номера требований (issue keys).

    Таким образом, значения обновляются автоматически каждый раз при редактировании и просмотре поля. Готовый плагин находится тут.

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

    Чтобы реализовать первое требование, нам следует организовать хранилище данных плагина, где мы сможем сохранять запрос (JQL) и другие настройки для разрабатываемого пользовательского поля. Чтобы хранить запрос поиска (JQL) целесообразно использовать контейнер данных для плагинов (com.atlassian.sal.api.pluginsettings.PluginSettings). Следует заметить, что при таком выборе у нас не будет проблем с переносимостью плагина на другие версии JIRA. Кроме того, если использовать использовать библиотеку сериализации Java-объектов (например, XStream) для представления объекта в виде строки, мы сможем хранить данные любой структуры. Чтобы использовать хранилище, необходимо добавить следующие строки в файл конфигурации плагина atlassian-plugin.xml:

    <!-- Plugin data manager  -->
    <component key="queryfields-config" name="Query issues custom fields plugin configuration" class="ru.mail.jira.plugins.lf.QueryFieldsMgrImpl"/>
    <!-- Components which required for injection  -->
    <component-import key="pluginSettingsFactory">
        <interface>com.atlassian.sal.api.pluginsettings.PluginSettingsFactory</interface>
    </component-import>
    

    а также определить интерфейс, выполняющий требуемые манипуляции,
    Исходный код интерфейса
    public interface QueryFieldsMgr
    {
        /**
         * Get "add null" option.
         */
        boolean getAddNull(long cfId, long projId);
    
        /**
         * Get stored data for the custom field.
         */
        String getQueryFieldData(long cfId, long projId);
    
        /**
         * Set "add null" option.
         */
        void setAddNull(long cfId, long projId, boolean data);
    
        /**
         * Put data for the custom field.
         */
        void setQueryFieldData(long cfId, long projId, String data);
    }
    


    и реализовать его:
    Исходный код реализация интерфейса
    import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
    
    public class QueryFieldsMgrImpl
        implements QueryFieldsMgr
    {
        /**
         * PlugIn key.
         */
        private static final String PLUGIN_KEY = "QUERY_LINKING_ISSUE_CFS";
    
        /**
         * Property separator.
         */
        private static final String VAL_SEPARATOR = "||";
    
        /**
         * Plug-In settings factory.
         */
        private final PluginSettingsFactory pluginSettingsFactory;
    
        /**
         * Constructor.
         */
        public QueryFieldsMgrImpl(
            PluginSettingsFactory pluginSettingsFactory)
        {
            this.pluginSettingsFactory = pluginSettingsFactory;
        }
    
        /**
         * Create property key.
         */
        private String createPropKey(long cfId, long projId)
        {
            return (cfId + VAL_SEPARATOR + projId);
        }
    
        @Override
        public boolean getAddNull(long cfId, long projId)
        {
            String addNull = (String)pluginSettingsFactory.createSettingsForKey(PLUGIN_KEY).get(createPropKey(cfId, projId).concat(".addnull"));
            return Boolean.parseBoolean(addNull);
        }
    
        @Override
        public String getQueryFieldData(
            long cfId,
            long projId)
        {
            return (String)pluginSettingsFactory.createSettingsForKey(PLUGIN_KEY).get(createPropKey(cfId, projId));
        }
    
        @Override
        public void setAddNull(long cfId, long projId, boolean data)
        {
            pluginSettingsFactory.createSettingsForKey(PLUGIN_KEY).put(createPropKey(cfId, projId).concat(".addnull"), Boolean.toString(data));
        }
    
        @Override
        public void setQueryFieldData(
            long cfId,
            long projId,
            String data)
        {
            pluginSettingsFactory.createSettingsForKey(PLUGIN_KEY).put(createPropKey(cfId, projId), data);
        }
    }
    



    После чего определённый менеджер данных можно использовать в любых сервлетах и расширениях стандартных Jira компонентов, например так:
    public class LinkerField
        extends TextCFType
        implements SortableCustomField<String>
    {
        /**
         * PlugIn data manager.
         */
        private final QueryFieldsMgr qfMgr;
    
        /**
         * Constructor.
         */
        public LinkerField(
            CustomFieldValuePersister customFieldValuePersister,
            GenericConfigManager genericConfigManager,
            QueryFieldsMgr qfMgr)
        {
            super(customFieldValuePersister, genericConfigManager);
            this.qfMgr = qfMgr;
        }
    ...
    


    Также хорошей практикой будет реализовать административную страницу настройки плагина. На странице имеет смысл распечатать список всех полей управления проектами и снабдить страницу контролами для настройки поля. Класс реализующий административную страницу лучше сделать наследником от com.atlassian.jira.web.action.JiraWebActionSupport. Пример класса – ru.mail.jira.plugins.lf.QueryFieldsConfig. Чтобы подключить к плагину, достаточно добавить в atlassian-plugin.xml следующие строки:
    <web-item key="queryfields-configuration" name="Query issues custom fields configuration link" section="admin_plugins_menu/mailru-admin-section" weight="95">
        <label key="queryfields.admin.title"/>
        <condition class="com.atlassian.jira.plugin.webfragment.conditions.JiraGlobalPermissionCondition">
            <param name="permission">admin</param>
        </condition>
        <link linkId="queryfields-configuration">/secure/QueryConfigCfsConfig.jspa</link>
    </web-item>
    
    <webwork1 key="queryfields_conf" name="Query issues custom fields configuration" class="java.lang.Object">
        <actions>
            <action name="ru.mail.jira.plugins.lf.QueryFieldsConfig" alias="QueryConfigCfsConfig">
                <view name="input">/templates/queryfieldsconf.vm</view>
                <view name="success">/templates/queryfieldsconf.vm</view>
            </action>
        </actions>
    </webwork1>
    


    Более подробно, как реализовывать URL-действия (actions) JIRA, можно посмотреть здесь.
    Кроме того следует помнить, что административную страницу следует защитить от XSRF-атак. Подробнее об этом читать здесь.

    Если мы не можем решить, каким образом мы будем отображать наше поле, то за основу можно взять простое текстовое поле (ru.mail.jira.plugins.lf.LinkerField) и расширить его. По текстовым полям невозможно строить диаграммы и собирать статистику, но этот функционал можно реализовать самим: ru.mail.jira.plugins.lf.LirkerFieldSearcher, ru.mail.jira.plugins.lf.LirkerFieldCustomFieldValueProvider, ru.mail.jira.plugins.lf.LirkerFieldSearchInputTransformer и ru.mail.jira.plugins.lf.LirkerFieldCustomFieldRenderer.

    После этого нам остаётся только запрограммировать внешний вид для редактирования и представление: edit-linkerfield.vm и view-linkerfield.vm.

    Хочу заметить, что если вы не знаете как запрограммировать представление, то советую посмотреть как реализованы стандартные поля: webapp/WEB-INF/classes/templates/plugins/fields.

    Данный подход можно использовать для реализации пользовательских полей любой сложности. Например, таким образом можно реализовать colorpicker, список из вычисляемых полей, формулы и так далее.

    Обработчики событий


    Второй плагин, который я хочу прокомментировать – JIRA MRIM Listener. Это плагин отправляет в Агент Mail.Ru оповещения о событиях в JIRA. Чтобы создать подобный плагин, достаточно реализовать всего один обработчик событий. Вот шаблон класса:
    Исходный код
    public class MyListener
       implements InitializingBean, DisposableBean
    {
       /*
        * Logger.
        */
       private static Log log = LogFactory.getLog(MyListener.class);
    
       /**
        * Event publisher.
        */
       private EventPublisher eventPublisher;
    
       /**
        * Constructor.
        */
       public MyListener (
           EventPublisher eventPublisher)
       {
           this.eventPublisher = eventPublisher;
       }
    
       @Override
       public void afterPropertiesSet()
       throws Exception
       {
           //--> register ourselves with the EventPublisher
           eventPublisher.register(this);
       }
    
       @Override
       public void destroy()
       throws Exception
       {
           //--> unregister ourselves with the EventPublisher
           eventPublisher.unregister(this);
       }
    
       @EventListener
       public void onIssueEvent(IssueEvent issueEvent)
       {
           // your code here
       }
    }
    


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

    Вместо заключения


    Тем, кто интересуется разработкой, но ещё никогда этим не занимался, я хочу порекомендовать книгу – JIRA Development Cookbook и следить за свободными плагинами на https://marketplace.atlassian.com/vendors/37127.

    Вот, собственно и всё, о чем я хотел вам рассказать. Если у вас появились вопросы или вы хотите поделиться своими соображениями по поводу JIRA и Confluence, буду рад пообщаться с вами в комментах. Кроме того, если у вас есть идеи свободного плагина – пишите, сделаю или помогу с реализацией.
    Mail.ru Group
    Строим Интернет

    Комментарии 7

      –1
      ;( Ребята, JIRA, а не Jira…
        +1
        Спасибо. Поправил
          0
          Андрей, зря поправил. Как раз Jira. Ребрендинг у них был год назад.
        0
        Андрей, спасибо за статью и полезный инструмент.

        Попробовали использовать, но нашли баг с select custom fields при формированием xml-фида в версии 4.4.4. JIRA.

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

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое