Я работаю разработчиком 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, буду рад пообщаться с вами в комментах. Кроме того, если у вас есть идеи свободного плагина – пишите, сделаю или помогу с реализацией.
ссылка на оригинал статьи http://habrahabr.ru/company/mailru/blog/149329/
Добавить комментарий