Как использовать soy, requirejs, backbone js в плагинах для Atlassian Jira

nxpd0vwkezmtouyath5ldmhwx2a.png
В этой статье разработаем плагин, который будет сохранять настройки плагина в Jira. Мы будем использовать библиотеки soy, requirejs, backbone js для отображения пользовательского интерфейса. Soy, requirejs, backbone js это встроенные в Jira библиотеки.
Цель статьи состоит в том, чтобы показать как можно использовать встроенные средства Jira для разработки пользовательского интерфейса.
Разработанный плагин будет содержать модуль webwork для сохранения параметров плагина в Jira. Параметры будут вводиться на двух экранах (по два параметра на каждом экране). Далее параметры будут упаковываться в json, который и будет сохраняться в Jira. Исходный код плагина можно посмотреть вот тут.

Создадим скелет плагина


Откроем терминал и выполним команду ниже:
atlas-create-jira-plugin
Ответим на вопросы в терминале вот так:

Define value for groupId: : ru.matveev.alexey.jira.tutorial.webworkui
Define value for artifactId: : webwork-soy-require-backbone
Define value for version:  1.0.0-SNAPSHOT: : 
Define value for package:  ru.matveev.alexey.jira.tutorial.webworkui: : 
Y: : Y


Внесем изменения в pom.xml


После создания скелета плагина необходимо внести изменения для корректной работы atlassian-spring-scanner 2.
Установим версию atlassian-spring-scanner в 2.0.0:

2.0.0


Изменим scope зависимости atlassian-spring-scanner-annotation с compile на provided:


    com.atlassian.plugin
    atlassian-spring-scanner-annotation
    ${atlassian.spring.scanner.version}
    provided


Удалим зависимость atlassian-spring-scanner-runtime.

Создадим сервис для получения и сохранения настроек плагина


Сначала создадим интерфейс для управления настройками плагина.

src/main/java/ru/matveev/alexey/jira/tutorial/webworkui/api/PluginSettingService.java
package ru.matveev.alexey.jira.tutorial.webworkui.api;

public interface PluginSettingService {
    String getConfigJson();
    void setConfigJson(String json);
}

Теперь сделаем реализацию интерфейса.

src/main/java/ru/matveev/alexey/jira/tutorial/webworkui/impl/PluginSettingServiceImpl.java
package ru.matveev.alexey.jira.tutorial.webworkui.impl;

import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.sal.api.pluginsettings.PluginSettings;
import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
import ru.matveev.alexey.jira.tutorial.webworkui.api.PluginSettingService;

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class PluginSettingServiceImpl implements PluginSettingService {

    public final PluginSettings pluginSettings;
    private static final String PLUGIN_STORAGE_KEY = "ru.matveev.alexey.jira.tutorial.webworkui.";
    private static final String CONFIG_JSON = "configjson";


    @Inject
    public PluginSettingServiceImpl(@ComponentImport PluginSettingsFactory pluginSettingsFactory) {
        this.pluginSettings = pluginSettingsFactory.createGlobalSettings();

    }

    private void setSettingValue(String settingKey, String settingValue) {
            this.pluginSettings.put(PLUGIN_STORAGE_KEY + settingKey, settingValue != null?settingValue:"");
    }

    private String getSettingValue(String settingKey) {
            return pluginSettings.get(PLUGIN_STORAGE_KEY + settingKey) != null?pluginSettings.get(PLUGIN_STORAGE_KEY + settingKey).toString():"";
    }

    @Override
    public String getConfigJson() {
        return getSettingValue(CONFIG_JSON);
    }

    @Override
    public void setConfigJson(String json) {
        setSettingValue(CONFIG_JSON, json);
    }
}

Методы getConfigJson и setConfigJson отвечают за получение и сохранение параметра в формате json.

Создадим webwork для управления настройками плагина


Откроем терминал в папке плагина и выполним команду ниже:
create-atlas-jira-plugin-module
На вопросы в терминале отвечаем следующим образом:

Choose a number (1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34): 31
Enter Plugin Module Name My Webwork Module: : Config
Show Advanced Setup? (Y/y/N/n) N: : Y
Module Key config: : webwork-config
Module Description The Config Plugin: : 
i18n Name Key config.name: : 
i18n Description Key config.description: : 
Enter Action Classname MyActionClass: : ConfigWebwork
Enter Package Name ru.matveev.alexey.jira.tutorial.webworkui.jira.webwork: :Enter Alias ConfigWebwork: : 
Enter View Name success: : success.soy
Enter Template Path /templates/webwork-config/configwebwork/success.soy.vm: : /templates/webwork-config/configwebwork/success.soy
Add Another View? (Y/y/N/n) N: : N
Add Another Action? (Y/y/N/n) N: : N
Add Another Plugin Module? (Y/y/N/n) N: : N


В результате будет создан файл src/main/java/ru/matveev/alexey/jira/tutorial/webworkui/jira/webwork/ConfigWebwork.java. Этот файл нужно изменить вот так:

src/main/java/ru/matveev/alexey/jira/tutorial/webworkui/jira/webwork/ConfigWebwork.java
package ru.matveev.alexey.jira.tutorial.webworkui.jira.webwork;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.atlassian.jira.web.action.JiraWebActionSupport;
import ru.matveev.alexey.jira.tutorial.webworkui.api.PluginSettingService;

import javax.inject.Inject;

public class ConfigWebwork extends JiraWebActionSupport
{
    private static final Logger log = LoggerFactory.getLogger(ConfigWebwork.class);
    private final PluginSettingService pluginSettingService;
    private String configJson;

    @Inject
    public ConfigWebwork(PluginSettingService pluginSettingService) {
        this.pluginSettingService = pluginSettingService;
    }

    @Override
    public String execute() throws Exception {
        super.execute();
        return SUCCESS;
    }

    public void doSave() {
        pluginSettingService.setConfigJson(configJson);
    }

 @ActionViewData
 public String getConfigJson() {
   return pluginSettingService.getConfigJson().isEmpty()?"{}":pluginSettingService.getConfigJson();
  }

 public void setConfigJson(String json) {
 this.configJson = json;
 }

}

Аннотация @ActionViewData необходима для того, чтобы параметр configJson был доступен в soy шаблоне.

Создадим web section и web item


Мы добавили webwork. Теперь добавим пункт меню, из которого будет запускаться webwork.
Открываем терминал и выполняем следующую команду:
create-atlas-jira-plugin-module
Отвечаем на вопросы следующим образом:

Choose a number (1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34): 30
Enter Plugin Module Name My Web Section: : Webwork Config Section
Enter Location (e.g. system.admin/mynewsection): admin_plugins_menu
Show Advanced Setup? (Y/y/N/n) N: : N
Add Another Plugin Module? (Y/y/N/n) N: : Y
Choose a number (1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34): 25
Enter Plugin Module Name My Web Item: : Webwork Config Item
Enter Section (e.g. system.admin/globalsettings): admin_plugins_menu/webwork-config-section 
Enter Link URL (e.g. /secure/CreateIssue!default.jspa): /secure/ConfigWebwork.jspa?        
Show Advanced Setup? (Y/y/N/n) N: : N
Add Another Plugin Module? (Y/y/N/n) N: : N


В результате мы создали пункт меню на странице Add-ons.

Создаем soy шаблон


Подробно про soy шаблоны можно почитать тут.
Мы создадим файл
src/main/resources/templates/webwork-config/configwebwork/success.soy.

src/main/resources/templates/webwork-config/configwebwork/success.soy
{namespace webwork.config}
/**
 * This template is needed for drawing the formview.
 */
{template .formview}
   {@param configJson: string}
   {webResourceManager_requireResource('ru.matveev.alexey.jira.tutorial.webworkui.webwork-soy-require-backbone:webwork-soy-require-backbone-resources')}
     
     
           
           
           
           
           my page page
      
     
        
the configJson Parameter
{/template}

В файле atlassian-plugin.xml в тег web-resource нужно добавить ссылку на созданный soy шаблон:



Теперь внесем изменения в atlassian-plugin.xml для того, чтобы при обращении к webwork был бы отображен созданный soy шаблон:

:webwork-soy-require-backbone-resources/webwork.config.formview


webwork-soy-require-backbone-resources — это атрибут name в теге web-resource, куда мы добавили ссылку на наш soy шаблон.
webwork.config.formview — namespace и название шаблона из soy файла.

Протестируем плагин


Откроем терминал в папке плагина и выполним следующую команду:
atlas-run
После того как Jira запустится нужно перейти в браузере по следующей ссылке:

localhost:2990/jira/secure/ConfigWebwork.jspa

Экран будет выглядеть вот так:

t7-gfmnnmqdqrqyl3nl2gaqpuj4.png

Можно попробовать ввести данные в поле Json и сохранить. Webwork работает.
Теперь нам нужно сделать так, чтобы было два экрана для заполнения параметров, и на последнем экране кнопка Save должна приводить все параметры в json формат и сохранять в настройках плагина.
Для управления логикой перемещения по экранам и приведением параметров в json формат мы будем использовать backbone js. Про backbone js можно почитать тут.

Создадим backbone модель


src/main/resources/js/webwork-config-model.js

define ('webwork/config/model', [
'jquery',
'backbone',
'underscore'
], function ($, Backbone, _) {
var WebConfigModel = Backbone.Model.extend ({
defaults: {
parameter1: '',
parameter2: '',
parameter3: '',
parameter4: ''
}
});
return {
Model: WebConfigModel
};
})


Для того, чтобы модель была доступна при загрузке soy шаблона, файл с моделью необходимо добавить в atlassian-plugin.xml в тег web-resource:


Создадим backbone вью


Я написал в коде комментарии для важных моментов.

src/main/resources/js/webwork-config-view.js
//define это директива requirejs и определяет модель как модуль webwork/config/view. Это позволяет нам определять зависимости в других файлах от модели.
define ('webwork/config/view', [
'jquery',
'backbone',
'underscore'
], function ($, Backbone, _) {
«use strict»;
var AppView = Backbone.View.extend ({
events: {
«click #config-save-button»: «saveConfig»,
«click #next-button»: «nextButton»,
«click #back-button»: «prevButton»

},
// функция, которая работает по кнопке Save. Сохраняет параметры с экрана в модель и преобразует параметры в json формат
saveConfig: function (){
this.model.set («parameter3», $(»#parameter3»).val ());
this.model.set («parameter4», $(»#parameter4»).val ());
$(»#configJson»).val (JSON.stringify (this.model));
},
// функция, которая работает по кнопке Next на первом экране. Сохраняет параметры с первого экрана в модель и рисует второй экран
nextButton: function (){
this.model.set («parameter1», $(»#parameter1»).val ());
this.model.set («parameter2», $(»#parameter2»).val ());
var template = webwork.config.page2({configJson:$(»#configJson»).val (), parameter3: this.model.get ('parameter3'), parameter4: this.model.get ('parameter4')});
$(»#container»).replaceWith (template);
$(»#configJson»).val (JSON.stringify (this.model));
},
// функция, которая работает по кнопке Back на втором экране. Сохраняет параметры со второго экрана в модель и рисует первый экран
prevButton: function (){
this.model.set («parameter3», $(»#parameter3»).val ());
this.model.set («parameter4», $(»#parameter4»).val ());
var template = webwork.config.page1({configJson:$(»#configJson»).val (), parameter1: this.model.get ('parameter1'), parameter2: this.model.get ('parameter2')});
$(»#container»).replaceWith (template);
$(»#configJson»).val (JSON.stringify (this.model));
},
initialize: function (){
this.render ();
},
render: function (){
var template = webwork.config.page1({configJson:$(»#configJson»).val (), parameter1: this.model.get ('parameter1'), parameter2: this.model.get ('parameter2')});
$(»#container»).replaceWith (template);
},
// это ссылка на главный контейнер. Вью будет ловить все события от элементов ниже этого элемента
el: '#maincontainer'
});
return {
View: AppView
};
})


Для того, чтобы вью была доступна при загрузке soy шаблона, файл с вью необходимо добавить в atlassian-plugin.xml в тег web-resource:



Создадим js file, чтобы настроить backbone модель и вью


src/main/resources/js/webwork-soy-require-backbone.js
require ([
'webwork/config/view',
'webwork/config/model',
'jquery',
'backbone',
'underscore'
], function (webworkConfigView, webworkConfigModel, $, Backbone, _) {
var webworkConfigModel = new webworkConfigModel.Model (JSON.parse ($(»#configJson»).val ()));
var actionsView = new webworkConfigView.View ({model: webworkConfigModel});

})


Наш js файл использует requirejs. Директива require позволяет добиться того, что файл будет загружен только после того, как все зависимости загружены. Мы определили следующие зависимости для нашего файла: webwork/config/view, webwork/config/model, query, backbone, underscore.

Добавим параметры необходимые для работы soy шаблонов


В тег web-resource в файле atlassian-plugin.xml нужно добавить:


  



Эти параметры позволяют обращаться к soy шаблону в js файлах.

Внесем изменения в success.soy


Я добавил комментарии к важным моментам

src/main/resources/templates/webwork-config/configwebwork/success.soy
{namespace webwork.config}
/**
 *  Этот шаблон запускается сразу из webwork. Здесь выводится json параметр. Далее этот шаблон сразу перерисовывается шаблоном page1. Шаблон нужен для того, чтобы получить json параметр и заполнить backbone model.
 */
{template .formview}
   {@param configJson: string}
   {webResourceManager_requireResource('ru.matveev.alexey.jira.tutorial.webworkui.webwork-soy-require-backbone:webwork-soy-require-backbone-resources')}
     
     
           
           
           
           
           my page page
      
     
        
{/template} /** * Это шаблон первого экрана. Он содержит parameter1 и parameter2. */ {template .page1} {@param configJson: string} {@param parameter1: string} {@param parameter2: string}
Value of Parameter 1
Value of Parameter 2
{/template} /** * Это шаблон второго экрана. Он содержит parameter3 и parameter4. */ {template .page2} {@param configJson: string} {@param parameter3: string} {@param parameter4: string}
Value of Parameter 3
Value of Parameter 4
{/template}


Протестируем приложение


Открываем терминал в папке плагина и запускаем:

atlas-run

После того как Jira запуститься, откройте браузер по ссылке:

http://localhost:2990/jira/secure/ConfigWebwork.jspa

Вы увидите следующий экран:

psqu4xcxomnbm5x9hxp61ruivju.png

Заполните параметры и нажмите на кнопку Next. Появится следующий экран:

fkc4dycvvypyaftokrt4-aos9dw.png

Заполните параметры 3 и 4 и нажмите на кнопку Save. Параметры будут сохранены в формате Json. Можно нажать на кнопку Back и вы перейдете к первому экрану.
Наш плагин работает.

© Habrahabr.ru