Rate this page:
Applicable: | Jira 7.0.0 and later. |
Level of experience: | This is an intermediate tutorial. You should have completed at least one beginner tutorial before working through this tutorial. See the list of tutorials in DAC. |
Time estimate: | It should take you approximately 1 hour to complete this tutorial. |
This tutorial shows you how to write an app that adds a dashboard item to gadget directory, so users can install it to customize their dashboards.
The dashboard item will have a static content rendered on server and dynamic content rendered client-side. Using this app you can track issues that are about to due.
You can add dashboard item elements to Jira by adding module definitions to the app descriptor. In this tutorial, you'll add the web section module to define the dashboard item in your Jira.
Your completed app will consist of the following components:
To complete this tutorial, you need to know the following:
We encourage you to work through this tutorial. If you want to skip ahead or check your work when you are finished, you can find the app source code on Atlassian Bitbucket.
To clone the repository, run the following command:
1
git clone https://bitbucket.org/atlassian_tutorial/dashboard-item-tutorial.git
Alternatively, you can download the source as a ZIP archive
In this step, you'll use an atlas-
command to generate stub code for your app.
To create an app skeleton, run the following command:
1
atlas-create-jira-plugin
To identify your app, enter the following information.
group-id |
|
artifact-id |
|
version |
|
package |
|
Confirm your entries when prompted.
The SDK generates the initial app project files in a directory named dashboard-item-tutorial
.
Delete the test directories.
Setting up testing for your app isn't part of this tutorial. To delete the generated test skeleton, run the following commands:
1 2
rm -rf ./src/test/java
rm -rf ./src/test/resources/
Delete the unneeded Java class files.
1
rm -rf ./src/main/java/com/example/plugins/tutorial/*
Import project in your favorite IDE.
It is a good idea to familiarize yourself with the project configuration file known as the POM (that is, Project Object Model definition file).
In this step, you'll review and tweak the pom.xml
file.
The POM is located at the root of your project and declares the project dependencies and other information.
pom.xml
file.Add your company or organization name and your website URL to the organization
element
(the following code blocks show how it looks in plain text):
1 2 3 4 5 6
``` xml
<organization>
<name>Example Company</name>
<url>http://www.example.com/</url>
</organization>
```
To add a meaningful description for your app, update the project description
element. For example:
1
<description>This plugin adds company links to a new menu in the JIRA header.</description>
Save the file.
Your stub code contains an app descriptor file called atlassian-plugin.xml
. This is an XML file that identifies
the app to the host application (that is, Jira) and defines the required app functionality.
1 2 3
Open the `atlassian-plugin.xml` file.
You should see something like this (comments removed):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<?xml version="1.0" encoding="UTF-8"?>
<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2">
<plugin-info>
<description>${project.description}</description>
<version>${project.version}</version>
<vendor name="${project.organization.name}" url="${project.organization.url}"/>
<param name="plugin-icon">images/pluginIcon.png</param>
<param name="plugin-logo">images/pluginLogo.png</param>
</plugin-info>
<resource type="i18n" name="i18n" location="dashboard-item-tutorial"/>
<web-resource key="dcd ashboard-item-tutorial-resources" name="dashboard-item-tutorial Web Resources">
<dependency>com.atlassian.auiplugin:ajs</dependency>
<resource type="download" name="dashboard-item-tutorial.css" location="/css/dashboard-item-tutorial.css"/>
<resource type="download" name="dashboard-item-tutorial.js" location="/js/dashboard-item-tutorial.js"/>
<resource type="download" name="images/" location="/images"/>
<context>dashboard-item-tutorial</context>
</web-resource>
</atlassian-plugin>
In this step, you'll create a dashboard item that will use context provider to pass value to Soy template.
Define a new dashboard item in the app descriptor
(that is, atlassian-plugin.xml
) file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
``` xml
<dashboard-item key="new-dashboard-item"
i18n-name-key="com.example.plugins.tutorial.dashboard.item.title">
<definition>
<title key="com.example.plugins.tutorial.dashboard.item.title"/>
<categories>
<category>Jira</category>
</categories>
<author>
<name>Author's name</name>
</author>
<thumbnail location="/download/resources/${atlassian.plugin.key}:dashboard-item-tutorial-resources/images/pluginLogo.png"/>
</definition>
<description key="com.example.plugins.tutorial.dashboard.item.description"/>
<resource name="view" type="soy" location=":dashboard-item-tutorial-resources/Dashboard.Item.Tutorial.Templates.Static"/>
<context-provider class="com.example.plugins.tutorial.DashboardItemContextProvider"/>
</dashboard-item>
```
* A `definition` element is used to display a dashboard item in gadget directory (that is, where users
search for gadgets).
You can reference pluginLogo.png
as a thumbnail location, because SDK included it in your
project and declared it in web-resource
element.
dashboard-item-tutorial-resources
is web resource's module key.
To use Soy template to render the dashboard item, update web-resources
element with the following:
1 2 3 4 5 6 7 8 9 10 11 12 13
<web-resource key="dashboard-item-tutorial-resources" name="dashboard-item-tutorial Web Resources">
<dependency>com.atlassian.auiplugin:ajs</dependency>
<transformation extension="soy">
<transformer key="soyTransformer"/>
</transformation>
<resource type="download" name="dashboard-item-tutorial.css" location="/css/dashboard-item-tutorial.css"/>
<resource type="download" name="dashboard-item-tutorial.js" location="/js/dashboard-item-tutorial.js"/>
<resource type="download" name="images/" location="/images"/>
<resource type="download" location="soy/dashboard-item.soy" name="soy/dashboard-item.soy.js"/>
<resource type="soy" location="/soy/dashboard-item.soy" name="Dashboard.Item.Tutorial.Templates.Static"/>
<context>atl.dashboard</context>
<context>dashboard-item-tutorial</context>
</web-resource>
Notice that we declared atl.dashboard
, so resources will be included on dashboard page.
Save the file.
src/main/resources
and create a soy
directory.dashboard-item.soy
file.Add the following code to the Soy template:
1 2 3 4 5 6 7 8 9
{namespace Dashboard.Item.Tutorial.Templates}
/**
* @param pluginName
* @param version
*/
{template .Static}
<div>Welcome to <span class="aui-lozenge aui-lozenge-success">{$pluginName}</span> v{$version}!</div>
{/template}
Save the file.
In this step, to make the new dashboard item work, you will create a context provider to inject
pluginName
and version
variables in Soy template.
src/main/java/com/example/plugins/tutorial
and create a new Java class DashboardItemContextProvider
.Add the following code to it:
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 35 36 37 38 39
``` java
package com.example.plugins.tutorial;
import com.atlassian.plugin.Plugin;
import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.PluginInformation;
import com.atlassian.plugin.PluginParseException;
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.plugin.web.ContextProvider;
import com.google.common.collect.Maps;
import java.util.Map;
@Scanned
public class DashboardItemContextProvider implements ContextProvider {
private final PluginAccessor pluginAccessor;
public DashboardItemContextProvider(
@ComponentImport PluginAccessor pluginAccessor) {
this.pluginAccessor = pluginAccessor;
}
@Override
public void init(final Map<String, String> params) throws PluginParseException {
}
@Override
public Map<String, Object> getContextMap(final Map<String, Object> context) {
final Map<String, Object> newContext = Maps.newHashMap(context);
Plugin plugin = pluginAccessor.getEnabledPlugin("com.example.plugins.tutorial.dashboard-item-tutorial");
newContext.put("version", plugin.getPluginInformation().getVersion());
newContext.put("pluginName", plugin.getName());
return newContext;
}
}
```
We used [Atlassian Spring Scanner](https://bitbucket.org/atlassian/atlassian-spring-scanner/src/1.2.x/)
to inject PluginAccessor
from host application.
Since we declared i18n keys in app descriptor, it's time to update dashboard-item-tutorial.properties
element in the atlassian-plugin.xml
file.
1 2 3 4
``` properties
com.example.plugins.tutorial.dashboard.item.title=Dashboard tutorial
com.example.plugins.tutorial.dashboard.item.description=Writing a dashboard item app tutorial
```
In Terminal window, run the following the SDK command:
1
atlas-run
This command starts a Jira instance and loads your app. In the output, look for a line that looks something like this:
1
[INFO] jira started successfully in 134s at http://localhost:2990/jira
It tells you that Jira instance has been started and shows you the Jira home page URL.
In a browser, go to Jira home page that is indicated in the Terminal output.
After you add dashboard item to dashboard you will see it in work.
The app works, but so far it doesn't do much. You will enhance it in the next steps.
Leave Jira running in browser for now.
So far you have created a simple app that renders a dashboard item server-side.
Dashboard item API provides a way to include JavaScript using AMD modules.
In this step, you will use JavaScript to make REST call and search for issues that are about to due.
Open the atlassian-plugin.xml
file and add the following inside dashboard-item
element:
1
<amd-module>dashboard-items/tutorial</amd-module>
Navigate to src/main/resources/soy
and open the dashboard-item.soy
file.
Create a div
element for dynamic content and add templates — first to render
issues, second to handle empty list.
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
{namespace Dashboard.Item.Tutorial.Templates}
/**
* @param pluginName
* @param version
*/
{template .Static}
<div id="dynamic-content"/>
<div>Welcome to <span class="aui-lozenge aui-lozenge-success">{$pluginName}</span> v{$version}!</div>
{/template}
/**
* @param issues
*/
{template .IssueList}
<table class="aui">
<thead>
<tr>
<th id="basic-icon"></th>
<th id="basic-fname">Issue key</th>
<th id="basic-lname">Due date</th>
</tr>
</thead>
<tbody>
{foreach $issue in $issues}
<tr>
<td headers="basic-number"><img src="{$issue.fields.issuetype.iconUrl}"/></td>
<td headers="basic-fname">{$issue.key}</td>
<td headers="basic-lname">{$issue.fields.duedate}</td>
</tr>
{/foreach}
</tbody>
</table>
<div class="buttons-container">
<div class="buttons">
<input class="button submit" type="submit" value="Refresh">
</div>
</div>
{/template}
/**
* Use this template if there is no issues in response
*/
{template .Empty}
No issues yet.
<div class="buttons-container">
<div class="buttons">
<input class="button submit" type="submit" value="Refresh">
</div>
</div>
{/template}
Navigate to src/main/resources/js/
and open the dashboard-item-tutorial.js
file.
To define AMD module, add the following JavaScript code to the file:
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 35 36 37 38 39 40 41 42 43
define('dashboard-items/tutorial', ['underscore', 'jquery', 'wrm/context-path'], function (_, $, contextPath) {
var DashboardItem = function (API) {
this.API = API;
this.issues = [];
};
/**
* Called to render the view for a fully configured dashboard item.
*
* @param context The surrounding <div/> context that this items should render into.
* @param preferences The user preferences saved for this dashboard item (e.g. filter id, number of results...)
*/
DashboardItem.prototype.render = function (context, preferences) {
this.API.showLoadingBar();
var $element = this.$element = $(context).find("#dynamic-content");
var self = this;
this.requestData().done(function (data) {
self.API.hideLoadingBar();
self.issues = data.issues;
if (self.issues === undefined || self.issues.length === 0) {
$element.empty().html(Dashboard.Item.Tutorial.Templates.Empty());
}
else {
$element.empty().html(Dashboard.Item.Tutorial.Templates.IssueList({issues: self.issues}));
}
self.API.resize();
$element.find(".submit").click(function (event) {
event.preventDefault();
self.render(element, preferences);
});
});
this.API.once("afterRender", this.API.resize);
};
DashboardItem.prototype.requestData = function () {
return $.ajax({
method: "GET",
url: contextPath() + "/rest/api/2/search?jql=due<=3d"
});
};
return DashboardItem;
});
In this snippet, we make a REST call to get issues that are due in 3 days (this is hardcoded for now) and render different templates depending on response.
Save the changes.
To test updates, rebuild your app using QuickReload:
1
atlas-package
Go to the local Jira instance that you left running before.
Disable caching in browser and refresh dashboard page. Your dashboard item should look like this.
Create few issues with due date in less than 3 days and click Refresh.
In this step, you will create a configuration screen for dashboard item that will be used to pass due date field in REST call.
Open the atlassian-plugin.xml
file, find dashboard-item
definition, and add configurable
attribute:
1 2 3
<dashboard-item key="new-dashboard-item"
i18n-name-key="com.example.plugins.tutorial.dashboard.item.title"
configurable="true">
Open the dashboard-item.soy
file. To create another Soy template to render configuration form,
add following to the file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
``` text
/**
* configuration
*/
{template .Configuration}
<form class="aui">
<div class="field-group">
<label for="due-date-input">Due Date
<span class="aui-icon icon-required">(required)</span></label>
<input class="text medium-field" type="text"
id="due-date-input" name="due-date-input" value="3d">
<div class="description">Syntax: 3(d|w|m) </div>
</div>
<div class="buttons-container">
<div class="buttons">
<input class="button submit" type="submit" value="Save" id="comment-save-button">
<a class="cancel" href="#">Cancel</a>
</div>
</div>
</form>
{/template}
```
Open the dashboard-item-tutorial.js
file. Because we added configurable
attribute, dashboard calls
renderEdit
method if dashboard item is not configured yet.
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 35 36
Add the following `renderEdit` method to the file:
``` javascript
DashboardItem.prototype.renderEdit = function (context, preferences) {
var $element = this.$element = $(context).find("#dynamic-content");
$element.empty().html(Dashboard.Item.Tutorial.Templates.Configuration());
this.API.once("afterRender", this.API.resize);
var $form = $("form", $element);
$(".cancel", $form).click(_.bind(function() {
if(preferences['due-date-input'])
this.API.closeEdit();
}, this));
$form.submit(_.bind(function(event) {
event.preventDefault();
var preferences = getPreferencesFromForm($form);
var regexp = /^\d+([dwm])$/;
if(regexp.test(preferences['due-date-input'])) {
this.API.savePreferences(preferences);
this.API.showLoadingBar();
}
}, this));
};
function getPreferencesFromForm($form) {
var preferencesArray = $form.serializeArray();
var preferencesObject = {};
preferencesArray.forEach(function(element) {
preferencesObject[element.name] = element.value;
});
return preferencesObject;
}
```
Update render
and requestData
methods to work with preferences:
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
DashboardItem.prototype.render = function (context, preferences) {
this.API.showLoadingBar();
var $element = this.$element = $(context).find("#dynamic-content");
var self = this;
this.requestData(preferences).done(function (data) {
self.API.hideLoadingBar();
self.issues = data.issues;
if (self.issues === undefined || self.issues.length === 0) {
$element.empty().html(Dashboard.Item.Tutorial.Templates.Empty());
}
else {
$element.empty().html(Dashboard.Item.Tutorial.Templates.IssueList({issues: self.issues}));
}
self.API.resize();
$element.find(".submit").click(function (event) {
event.preventDefault();
self.render(context, preferences);
});
});
this.API.once("afterRender", this.API.resize);
};
DashboardItem.prototype.requestData = function (preferences) {
return $.ajax({
method: "GET",
url: contextPath() + "/rest/api/2/search?maxResults=10&jql=due<=" + preferences['due-date-input']
});
};
Save the changes.
Rebuild your app and test changes, your configuration screen should look like this:
Your app JAR file is located at target\dashboard-item-tutorial-1.0-SNAPSHOT.jar
.
You can install the app using the Universal Plugin Manager.
Rate this page: