How to use soy, requirejs, backbone js in Atlassian Jira plugins
- Tutorial
In this article we will develop a plugin that will save the plugin settings in Jira. We will use the soy, requirejs, backbone js libraries to display the user interface. Soy, requirejs, backbone js are libraries built into Jira.
The purpose of the article is to show how Jira’s built-in tools can be used to develop a user interface.
The developed plug-in will contain a webwork module for saving plug-in parameters in Jira. Parameters will be entered on two screens (two parameters on each screen). Further, the parameters will be packaged in json, which will be stored in Jira. The source code of the plugin can be viewed here .
Create the skeleton of the plugin
Open the terminal and execute the command below:
atlas-create-jira-plugin
Answer the questions in the terminal like this:
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
Make changes to pom.xml
After creating the plug-in skeleton, you need to make changes for the correct operation of atlassian-spring-scanner 2 .
Install the atlassian-spring-scanner version in 2.0.0:
<atlassian.spring.scanner.version>2.0.0</atlassian.spring.scanner.version>
Change the scope of the atlassian-spring-scanner-annotation dependencies from compile to provided:
<dependency><groupId>com.atlassian.plugin</groupId><artifactId>atlassian-spring-scanner-annotation</artifactId><version>${atlassian.spring.scanner.version}</version><scope>provided</scope></dependency>
Remove the atlassian-spring-scanner-runtime dependency.
Create a service for getting and saving plugin settings
First, create an interface to manage the plugin settings.
src / main / java / ru / matveev / alexey / jira / tutorial / webworkui / api / PluginSettingService.java
package ru.matveev.alexey.jira.tutorial.webworkui.api;
publicinterfacePluginSettingService{
String getConfigJson();
voidsetConfigJson(String json);
}
Now we will implement the interface.
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;
@NamedpublicclassPluginSettingServiceImplimplementsPluginSettingService{
publicfinal PluginSettings pluginSettings;
privatestaticfinal String PLUGIN_STORAGE_KEY = "ru.matveev.alexey.jira.tutorial.webworkui.";
privatestaticfinal String CONFIG_JSON = "configjson";
@InjectpublicPluginSettingServiceImpl(@ComponentImport PluginSettingsFactory pluginSettingsFactory){
this.pluginSettings = pluginSettingsFactory.createGlobalSettings();
}
privatevoidsetSettingValue(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():"";
}
@Overridepublic String getConfigJson(){
return getSettingValue(CONFIG_JSON);
}
@OverridepublicvoidsetConfigJson(String json){
setSettingValue(CONFIG_JSON, json);
}
}
The getConfigJson and setConfigJson methods are responsible for getting and saving a parameter in json format.
Create webwork to manage plugin settings
Open the terminal in the plugin folder and execute the command below:
create-atlas-jira-plugin-module
We answer the questions in the terminal as follows:
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
As a result, the file src / main / java / ru / matveev / alexey / jira / tutorial / webworkui / jira / webwork / ConfigWebwork.java will be created. This file needs to be changed like this:
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;
publicclassConfigWebworkextendsJiraWebActionSupport{
privatestaticfinal Logger log = LoggerFactory.getLogger(ConfigWebwork.class);
privatefinal PluginSettingService pluginSettingService;
private String configJson;
@InjectpublicConfigWebwork(PluginSettingService pluginSettingService){
this.pluginSettingService = pluginSettingService;
}
@Overridepublic String execute()throws Exception {
super.execute();
return SUCCESS;
}
publicvoiddoSave(){
pluginSettingService.setConfigJson(configJson);
}
@ActionViewDatapublic String getConfigJson(){
return pluginSettingService.getConfigJson().isEmpty()?"{}":pluginSettingService.getConfigJson();
}
publicvoidsetConfigJson(String json){
this.configJson = json;
}
}
The @ActionViewData annotation is necessary for the configJson parameter to be available in the soy template.
Create a web section and web item
We added webwork. Now add a menu item from which webwork will be launched.
Open the terminal and execute the following command:
create-atlas-jira-plugin-module
Answer the questions as follows:
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
As a result, we created a menu item on the Add-ons page.
Create a soy template
Details about soy templates can be read here .
We will create the
src / main / resources / templates / webwork-config / configwebwork / success.soy file.
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')}
<html><head><metacharset="utf-8"/><metaname="decorator"content="atl.admin"><metaname="admin.active.section"content="admin_plugins_menu/telegram-config-section"><metaname="admin.active.tab"content="telegram-general-config-item"><title>my page page</title></head><body><divid="container"><formclass="aui"action="ConfigWebwork!save.jspa"method="POST"><divclass="field-group"><labelfor="configJson">Json</label><inputclass="text long-field"type="text"id="configJson"name="configJson"placeholder="Json String"value="{$configJson}"><divclass="description">the configJson Parameter</div></div><divclass="buttons-container"><divclass="buttons"><inputclass="button submit"type="submit"value="Save"id="config-save-button"><aclass="cancel"href="#">Cancel</a></div></div></form></div></body></html>
{/template}
In the atlassian-plugin.xml file, add a link to the created soy template to the web-resource tag:
<resourcetype="soy"name="webwork-config"location="/templates/webwork-config/configwebwork/success.soy"/>
Now we will make changes to atlassian-plugin.xml so that when accessing webwork, the created soy template would be displayed:
<viewname="success"type="soy">:webwork-soy-require-backbone-resources/webwork.config.formview</view>
webwork-soy-require-backbone-resources is the name attribute in the web-resource tag, where we added a link to our soy template.
webwork.config.formview - the namespace and name of the template from the soy file.
Test plugin
Open the terminal in the plug-in folder and execute the following command:
atlas-run
After Jira launches, go to the browser using the following link:
localhost : 2990 / jira / secure / ConfigWebwork.jspa The
screen will look like this:
You can try to enter data in the Json field and save. Webwork works.
Now we need to make it so that there are two screens to fill in the parameters, and on the last screen, the Save button should bring all the parameters in json format and save in the plugin settings.
To control the logic of moving around the screens and bringing the parameters to the json format, we will use backbone js. About backbone js can be read here .
Create a backbone model
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
};
})
'jquery',
'backbone',
'underscore'
], function($, Backbone, _) {
var WebConfigModel = Backbone.Model.extend({
defaults: {
parameter1: '',
parameter2: '',
parameter3: '',
parameter4: ''
}
});
return {
Model: WebConfigModel
};
})
In order for the model to be available when loading the soy template, the file with the model must be added to atlassian-plugin.xml in the web-resource tag:
<resourcetype="download"name="webwork-config-model.js"location="/js/webwork-config-model.js"/>
Create a backbone view
I wrote comments in the code for important points.
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
};
})
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
};
})
In order for the twist to be available when the soy template is loaded, the twist file must be added to atlassian-plugin.xml in the web-resource tag:
<resourcetype="download"name="webwork-config-view.js"location="/js/webwork-config-view.js"/>
Create a js file to customize the backbone model and view
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});
})
'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});
})
Our js file uses requirejs. The require directive allows you to ensure that the file is loaded only after all the dependencies are downloaded. We defined the following dependencies for our file: webwork / config / view, webwork / config / model, query, backbone, underscore.
Add the parameters necessary for the work of soy templates
Add to the web-resource tag in the atlassian-plugin.xml file:
<transformationextension="soy"><transformerkey="soyTransformer"/></transformation><resourcename="success-soy.js"type="download"location="/templates/webwork-config/configwebwork/success.soy"/>
These options allow you to access the soy template in js files.
Make changes to success.soy
I added comments on important points.
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')}
<html><head><metacharset="utf-8"/><metaname="decorator"content="atl.admin"><metaname="admin.active.section"content="admin_plugins_menu/telegram-config-section"><metaname="admin.active.tab"content="telegram-general-config-item"><title>my page page</title></head><body><divid="maincontainer"><divid="container"><inputclass="text long-field hidden"type="text"id="configJson"name="configJson"placeholder="Json String"value="{$configJson}"></div></div></body></html>
{/template}
/**
* Это шаблон первого экрана. Он содержит parameter1 и parameter2.
*/
{template .page1}
{@param configJson: string}
{@param parameter1: string}
{@param parameter2: string}
<divid="container"><formclass="aui"><divclass="field-group"><labelfor="parameter1">Parameter 1</label><inputclass="text long-field"type="text"id="parameter1"name="parameter1"placeholder="Parameter1 value"value="{$parameter1}"><divclass="description">Value of Parameter 1</div></div><divclass="field-group"><labelfor="parameter2">Parameter 2</label><inputclass="text long-field"type="text"id="parameter2"name="parameter2"placeholder="Parameter2 value"value="{$parameter2}"><divclass="description">Value of Parameter 2</div></div><divclass="field-group"><inputclass="text long-field hidden"type="text"id="configJson"name="configJson"placeholder="Json String"value="{$configJson}"></div><divclass="buttons-container"><divclass="buttons"><aclass="cancel"href="#">Cancel</a><inputclass="button submit"type="submit"value="Next"id="next-button"></div></div></form></div>
{/template}
/**
* Это шаблон второго экрана. Он содержит parameter3 и parameter4.
*/
{template .page2}
{@param configJson: string}
{@param parameter3: string}
{@param parameter4: string}
<divid="container"><formclass="aui"action="ConfigWebwork!save.jspa"method="POST"><divclass="field-group"><labelfor="parameter1">Parameter 3</label><inputclass="text long-field"type="text"id="parameter3"name="parameter3"placeholder="Parameter3 value"value="{$parameter3}"><divclass="description">Value of Parameter 3</div></div><divclass="field-group"><labelfor="parameter4">Parameter 4</label><inputclass="text long-field"type="text"id="parameter4"name="parameter4"placeholder="Parameter4 value"value="{$parameter4}"><divclass="description">Value of Parameter 4</div></div><divclass="field-group"><inputclass="text long-field hidden"type="text"id="configJson"name="configJson"placeholder="Json String"value="{$configJson}"></div><divclass="buttons-container"><divclass="buttons"><inputclass="button submit"type="submit"value="Back"id="back-button"><inputclass="button submit"type="submit"value="Save"id="config-save-button"></div></div></form></div>
{/template}
Test the application
Open the terminal in the plug-in folder and launch:
atlas-run
After Jira is launched, open the browser by the link:
http://localhost:2990/jira/secure/ConfigWebwork.jspa
You will see the following screen:
Fill in the settings and click the Next button. The following screen will appear:
Fill in the parameters 3 and 4 and click on the Save button. Parameters will be saved in Json format. You can click on the Back button and you will go to the first screen.
Our plugin works.