| In JIRA 4, portlets have been superceded by gadgets. For details, please see [Gadget Development]. |
Overview
A portlet displays summary information on project/issue data through the JIRA dashboard. This information can range from statistical data to quick links for commonly used filters.
With JIRA 3.0, the introduction of the plugin system aimed to provide a simple point of extensibility for custom features users may wish to add to JIRA. This tutorial will describe how to create a custom portlet within JIRA 3.0, using this plugin interface.
The following articles provide further detailed information on writing a JIRA 3 plugin:
- JIRA Plugin Guide,
- How to create a new Custom Field Type
- How to create a JIRA Report
- Vincent Massol's tutorial
A Portlet Plugin Module Primer
In order to make a custom portlet available within JIRA, it is necessary to create a portlet plugin module. As with all plugin modules, the portlet plugin will consist of the following components:
- Java classes encapsulating portlet logic
- Resource templates for display of the portlet
- Plugin descriptor to enable the portlet module in JIRA
all contained within a single JAR file.
Portlet Logic
The Java classes include the necessary logic to retrieve the data used in configuring and displaying the portlet. The module class must implement com.atlassian.jira.portal.Portlet and will usually extend com.atlassian.jira.portal.PortletImpl. The PortletImpl interface provides methods to initialise the portlet through the parameters defined in the descriptor (name, key, thumbnail image, etc.) and retrieve the portlet template location. It is also possible to extend existing portlets to add further custom functionality.
Resource Templates
The second component consists of templates used to render the portlet - Velocity templates can be used here. The templates can include:
- Dashboard view - the actual dashboard view
The plugin system parses the atlassian-plugin.xml file for any configuration properties associated with the portlet that are required in order to display the portlet. The plugin system constructs a suitable configuration screen requesting the user to specify values for these properties.
Other Resources
It is possible to include i18n property files also - so as to allow other users to easily translate the strings used in the portlet for different languages. The following portlet examples are fully internationalized and include default property files.
Portlets are added through the dashboard configuration screen. The portlet selection displays the title and description along with a thumbnail image which links to a full image preview of the actual portlet display. It is necessary to include these image files (both the thumbnail and full image) within the final plugin JAR. To make the process as simple as possible, the images must be manually extracted from the JAR and copied to the webapp/portlets/dashboard/thumbnails directory in order for JIRA to access them. The full image location is specified in the atlassian-plugin.xml file - JIRA will search for the 'thumbnail image' in the same location.
Plugin Descriptor
The portlet module descriptor is the only mandatory part of the plugin. It must be called atlassian-plugin.xml and be located in the root of the JAR file.
Here is a sample portlet module descriptor element:
<!--
The module class must implement
com.atlassian.jira.portal.Portlet
and will usually extend:
com.atlassian.jira.portal.PortletImpl
-->
<portlet key="assignedtome" name="Assigned Issues" class="com.atlassian.jira.portal.portlets.AssignedToMePortlet">
<description key="portlet.assignedtome.description">i18n description</description>
<!-- this template produces the eventual HTML of the portlet -->
<resource type="velocity" name="view" location="templates/plugins/jira/portlets/assignedtome.vm" />
<label key="portlet.assignedtome.name" />
<!-- an optional thumbnail image used to preview the portlet for users -->
<thumbnail>portlets/dashboard/thumbnails/assigned.gif</thumbnail>
<!-- the permissions required to add this portlet (optional - not often required) -->
<permission>assignable</permission>
<objectdescriptor key="portlet.assignedtome.display.name" />
<!-- the properties of this report which the user must select before running it -->
<properties>
<property>
<key>numofentries</key>
<name>portlet.assignedtome.field.numofentries.name</name>
<description>portlet.assignedtome.field.numofentries.description</description>
<type>long</type>
<default>10</default>
</property>
</properties>
</portlet>
In this sample, the portlet logic is encapsulated in the AssignedToMePortlet Java class. The view template location is specified in the templates/plugins/jira/portlets directory. The preview image is located in the portlets/dashboard/thumbnails directory. The Assignable permission is required for users to add this portlet to their dashboards. Following that, the parameters required to configure the portlet are specified - in this case, a numeric field specifying the number of issues to display in the portlet. You can find the list of available permissions here
JIRA Development Kit
You can choose to develop your plugins however you wish. However, we recommend using Maven 1.0 and the JIRA Plugin Development Kit.
Maven is an ant-like build tool that downloads any specified project dependencies automatically (just one of the many features).
The JIRA Plugin Development Kit consists of a number of examples (including the ones discussed here) to help developers extend JIRA through the plugin interface as easily as possible.
Once the JIRA Plugin Development Kit has been setup, you need only run the command:
maven jar
to build the desired plugin. Using Maven is not a requirement - you can use ant or any other build tool, however, it will make your life a lot easier.
Examples
The following examples detail how to extend an existing system portlet and how to create custom portlets. Each example is included in the JIRA Plugin Development Kit. When compiled, the atlassian-jira-plugin-portlets-example-1.0.jar JAR includes each portlet and can be copied to the JIRA lib directory in order to make the portlets available within the system.
Example 1 - Extending an Existing System Portlet
In this example, an existing system portlet - 'Assigned To Me' - is extended to display the status of the issue. The original portlet displays a specified number of issues assigned to the current user, displaying the issue key, summary and priority.
This full source of this example is included in the development kit - the files of interest are:
- src/etc/templates/historyportlet/assignedtomeextended.vm
- src/etc/templates/historyportlet/issuesummaryextended.vm
- src/etc/atlassian_plugins.xml
- src/etc/com/atlassian/jira/plugin/portlet/example/assigned/assigned_portlet.properties
- src/etc/portlets/dashboard/thumbnails/assignedextended.png
- src/etc/portlets/dashboard/thumbnails/corner_assignedextended.png
In order to do this, it is only necessary to edit the view templates and i18n property files as all other aspects of the portlet can be reused. The information required to display the status is already passed to the template in the form of the issue itself - so the portlet logic does not need to be modified.
Firstly, the atlassian-plugin.xml file is created to include a new portlet module:
<atlassian-plugin key="com.atlassian.jira.plugin.portlet.example" name="JIRA Portlet Examples Plugin">
<plugin-info>
<description>Portlet Examples Plugin.</description>
<version>1.0</version>
<application-version min="3.0" max="3.0"/>
<vendor name="Atlassian Software Systems Pty Ltd" url="http://www.atlassian.com"/>
</plugin-info>
<!-- An simple example of customising an existing portlet - adding the status of an issue to the template -->
<portlet key="assignedtomeextended" name="Example: Assigned IssNues Extended"
class="com.atlassian.jira.portal.portlets.AssignedToMePortlet">
<description key="portlet.asssignedtome.extended.description">i18n description</description>
<resource type="velocity" name="view" location="templates/assignedportlet/assignedtomeextended.vm" />
<resource type="i18n" name="i18n" location="com.atlassian.jira.plugin.portlets.example.assigned.assigned_portlet" />
<label key="portlet.assignedtome.extended.name" />
<thumbnail>portlets/dashboard/thumbnails/assignedextended.png</thumbnail>
<permission>assignable</permission>
<objectdescriptor key="portlet.assignedtome.extended.display.name" />
<properties>
<property>
<key>numofentries</key>
<name>portlet.assignedtome.field.numofentries.name</name>
<description>portlet.assignedtome.field.numofentries.description</description>
<type>long</type>
<default>10</default>
</property>
</properties>
</portlet>
</atlassian-plugin>
As can be seen, the original AssignedToMePortlet Java class is reused - no code changes are necessary.
The remainder of the portlet definition specifies the following information:
- the name and description properties
- the location of the i18n files
- the location of the thumbnail files
- the parameters required to configure the portlet
The main change to note is the reference to a modified view template - one that displays the status. The modified view templates simply over-ride the templates used by the original 'Assigned to Me' portlet. The issuesummaryextended.vm includes the following section in order to display the issue status in the dashboard view:
<td nowrap width=1%>
#displayConstantIcon($issue.getStatusObject())
</td>
The i18n property strings have also been modified (in order to change the portlet name and description). These i18n property strings are defined in files that are included in the final JAR.
Finally, the thumbnails displaying a preview of the portlet are included in the JAR also. However, the thumbnails need to be manually copied to the webapp/portlets/dashboard/thumbnails directory in order for JIRA to access them.
The portlet will appear on the dashboard once selected, displaying the status of each issue.
Example 2 - Recently Updated Issues
In this example, a custom portlet is coded to display the most recently updated issues within a specific project on the dashboard. The portlet can be configured to display a specified number of issues over a specified number of days in the past.
This full source of this example is included in the development kit - the files of interest are:
- src/etc/templates/historyportlet/history.vm
- src/etc/templates/historyportlet/issuesummaryhistory.vm
- src/etc/atlassian_plugins.xml
- src/etc/com/atlassian/jira/plugin/portlet/example/history/history_portlet.properties
- src/etc/portlets/dashboard/thumbnails/history.png
- src/etc/portlets/dashboard/thumbnails/corner_history.png
- src/java/com/atlassian/jira/plugin/portlet/example/history/HistoryPortlet.java
Firstly, the atlassian-plugin.xml file is created/modified to include the new portlet:
<!-- A simple example portlet - displays recently issues updated for a specific user -->
<portlet key="history" name="Example: Recently Updated Issues"
class="com.atlassian.jira.plugin.portlet.example.history.HistoryPortlet">
<description key="portlet.history.description">i18n description</description>
<resource type="velocity" name="view" location="templates/historyportlet/history.vm" />
<resource type="i18n" name="i18n" location="com.atlassian.jira.plugin.portlets.example.history.history_portlet" />
<label key="portlet.history.name" />
<thumbnail>portlets/dashboard/thumbnails/assignedextended.png</thumbnail>
<objectdescriptor key="portlet.history.display.name" />
<properties>
<property>
<key>projectid</key>
<name>portlet.history.field.project.name</name>
<description>portlet.history.field.project.description</description>
<type>select</type>
<values class="com.atlassian.jira.portal.ProjectValuesGenerator"/>
</property>
<property>
<key>dayshistory</key>
<name>portlet.history.field.dayshistory.name</name>
<description>portlet.history.field.dayshistory.description</description>
<type>long</type>
<default>3</default>
</property>
<property>
<key>numofentries</key>
<name>portlet.history.field.numofentries.name</name>
<description>portlet.history.field.numofentries.description</description>
<type>long</type>
<default>10</default>
</property>
</properties>
</portlet>
As can be seen, a custom Java class - HistoryPortlet - encapsulates the logic required for configuring and displaying this portlet.
The remainder of the portlet definition in atlassian-plugin.xml specifies the following information:
- the name and description properties
- the location of the i18n files
- the location of the thumbnail files
- the parameters required to configure the portlet
In this case, the portlet requires three parameters to correctly display the data:
- projectid - a select field specifying the project on which the portlet will focus. The possible project options available are retrieved through another JIRA class - com.atlassian.jira.portal.ProjectValuesGenerator.
- dayshistory - a numeric field specifying the number of days to look back over.
- numofentries - a numeric field specifying the number of issues to display.
The class HistoryPortlet encapsulates the logic necessary to retrieve the issues most recently updated within the specified project over the specified number of days.
The view templates are very similar to the ones used in the 'Assigned To Me Extended' example discussed above - the main changes taking place with the name and description properties.
Again, as with the previous example, the i18n property files and image previews are also included. The thumbnails need to be manually copied to the webapp/portlets/dashboard/thumbnails directory in order for JIRA to access them.
The portlet will appear on the dashboard once selected, displaying the specified number of recently updated issues within the specified project.






Comments (5)
Jan 13, 2009
Scott Harman says:
HI guys the following block of code described above throws an error: window....HI guys the following block of code described above throws an error:
<td nowrap width=1%> #set ($status = ${constantsManager.getStatus($issue.getString("status"))}) #if (${status.getString("iconurl").startsWith("http://")}) <img xsrc="${status.getString("iconurl")}" height=16 width=16border=0 align=absmiddle alt="${status.getString("name")}"> #else<img xsrc="${baseurl}${status.getString("iconurl")}" height=16width=16 border=0 align=absmiddle alt="${status.getString("name")}"> #end </td>2009-01-13 16:19:58,843 http-80-Processor25 ERROR [velocity] Method getString threw exception for reference $issue in template templates/assignedportlet/issuesummaryextended.vm at [22,50] 2009-01-13 16:19:58,843 http-80-Processor25 ERROR [velocity] Method getString threw exception for reference $constantsManager in template templates/assignedportlet/issuesummaryextended.vm at [22,21] 2009-01-13 16:19:58,843 http-80-Processor25 ERROR [com.atlassian.velocity.DefaultVelocityManager] MethodInvocationException occurred getting message body from Velocity: java.lang.UnsupportedOperationException: This code or velocity template expects a GenericValue, but received an Issue. We need to recodeJan 14, 2009
Scott Harman says:
replaced the <td> block above with: <td nowrap width=1%> &nbs...replaced the <td> block above with:
<td nowrap width=1%>
#displayConstantIcon($issue.getStatusObject())
</td>
Jul 15, 2009
Jeff Kirby says:
Under Other Resources I read, "It is possible to include i18n property files..."...Under Other Resources I read, "It is possible to include i18n property files...". I've found that you must include i18n files because the label has to exist and it has to be specified in an i18n file or else you get a NullPointerException when you try to add the portlet.
Jul 20, 2009
Dylan Etkin says:
Jeff, I believe you are right that you need to have the label field, but I think...Jeff, I believe you are right that you need to have the label field, but I think that if you just put in the text you want for the label that this will be seen as a missed i18n key and that the default behavior for a missed key is to use the key as the actual text.
So you should not need to provide the i18n file, if you want to use only one language try using the actual string in place of the i18n key.
Jul 20, 2009
Jeff Kirby says:
Hi Dylan, Ah, right! That does work. Though then you get a nas...Hi Dylan,
Ah, right! That does work. Though then you get a nasty message in the logs.