Tutorial - Writing gadgets for JIRA

Applicable:

This tutorial applies to JIRA 4.4.x and JIRA 5.0.

Level of experience:

Advanced. Our tutorials are classified as 'beginner', 'intermediate' and 'advanced'. This one is at 'advanced' level. If you have never developed a plugin before, we advise you to try a beginner tutorial first.

On this page:

Source code

The source code of the plugin used in this tutorial is available in the Atlassian public source repository. You can check out the source code from Bitbucket:

New to Bitbucket? See Getting Started with Bitbucket.

Overview

In this tutorial, we're going to create a new Atlassian gadget for JIRA, bundle it inside a plugin, use a REST resource to provide it with data, and have the gadget talk to the resource. The gadget we'll make will list the projects in your JIRA instance that the current user can see.

Atlassian has implemented some of the OpenSocial specification in all of its applications. The gadget we'll write here is actually an OpenSocial gadget (also called a "Google Gadget") with some special sauce for JIRA. We'll assume a passing familiarity with gadgets, but in case they're new to you, there's a space devoted to gadget authoring.

Our gadget plugin will consist of the following components:

  • A gadget specification file to hold the gadget's XML and JavaScript.
  • Java classes implementing the REST resource the gadget will use.
  • Resources for display of the plugin UI.
  • A plugin descriptor to enable the plugin module in JIRA.

All these components will be contained within a single JAR file. Each component is further discussed in the examples below.

Step 1. Create the Plugin Project

Use the appropriate atlas-create- application -plugin command to create your plugin. For example, atlas-create-jira-plugin or atlas-create-confluence-plugin.

When prompted, enter the following information to identify your plugin:

  • group-id: com.atlassian.plugins.tutorial
  • artifact-id: jira-gadget-tutorial-plugin
  • version: 1.0-SNAPSHOT
  • package: com.atlassian.plugins.tutorial

Step 2. Create the Gadget Specification

For complete information on making a gadget spec, see the Atlassian Gadgets documentation.

First we will create the gadget spec file. This is src/main/resources/gadget.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
    <ModulePrefs title="__MSG_gadget.title__" directory_title="__MSG_gadget.title__"
        description="__MSG_gadget.description__">
        <Optional feature="gadget-directory">
            <Param name="categories">
                JIRA
            </Param>
        </Optional>
        <Optional feature="atlassian.util" />
        <Optional feature="auth-refresh" />
        <Require feature="views" />
        <Require feature="settitle"/>
        <Require feature="oauthpopup" />
        #oauth
        <Locale messages="__ATLASSIAN_BASE_URL__/download/resources/jira-gadget-tutorial-plugin/i18n/ALL_ALL.xml"/>
    </ModulePrefs>
    <Content type="html" view="profile">
        <!-- omitted for now -->
    </Content>
 </Module>

The <ModulePrefs> section is the metadata container for the gadget: title, directory title, description and so on. The <Content> section contains the HTML and/or JavaScript that drive the gadget's behaviour. We have left it empty here while we look more closely at <ModulePrefs>.

There are a few important things to notice:

  • The __MSG_gadget.title__ and __MSG_gadget.description__ properties are substituted at runtime by the gadget's message bundles, specified in <Locale> elements. The bundle is at src/main/resources/i18n/ALL_ALL.xml.
  • The <Locale> element tells the gadget where to find the message bundles. The messages attribute takes a URL. We'll show how to expose the message bundle in the next step.
  • The 'oauthpopup' and 'auth-refresh' items are needed for the gadget to authenticate to JIRA. See more about gadget authentication.
  • Finally, notice the <Optional> 'gadget-directory' feature. It specifies that the gadget is for JIRA and should be placed in the 'JIRA' category in the gadget directory browser. Without this, it is much harder to find and use gadgets from the directory browser.

Here's the XML message bundle itself under src/main/resources/i18n/ALL_ALL.xml:

<messagebundle>
    <msg name="gadget.title">Test JIRA Tutorial Gadget</msg>
    <msg name="gadget.description">A sample gadget to install into JIRA.</msg>
</messagebundle>

Step 3. Customise the Plugin Descriptor and Maven POM

Now we need to edit the plugin descriptor at src/main/resources/atlassian-plugin.xml to give our plugin a unique key, and some meta information about this plugin.

For our plugin, we will start with a module declaration for the gadget spec:

    <gadget key="tutorial-gadget" name="JIRA Tutorial Gadget" location="gadget.xml"/>

A gadget is a module in atlassian-plugin.xml, like any other JIRA module. There are three required properties to note:

  • key must be unique for all modules in this plugin
  • name is the name under which the plugin module will be displayed in the JIRA administration console
  • location is the path to the gadget spec file, relative to src/main/resources

Next, we'll add the <resource> for the message bundle:

    <resource type="download" name="i18n/ALL_ALL.xml" location="i18n/ALL_ALL.xml">
        <param name="content-type" value="text/xml; charset=UTF-8"/>
    </resource>

Building URLs to message bundles

At gadget render time, __ATLASSIAN_BASE_URL__ in the gadget's xml file will be automatically substituted with JIRA's configured base URL. The rest of the download URL is made from the atlassian-plugin.xml plugin key (in this case jira-gadget-tutorial-plugin) and the <resource>'s key value (in this case i18n/ALL_ALL.xml).

Below is the atlassian-plugin.xml for our plugin:

<atlassian-plugin key="jira-gadget-tutorial-plugin"
                  name="JIRA Gadget Tutorial Plugin"
                  pluginsVersion="2">
    <!-- Contains plugin metadata. -->
    <plugin-info>
        <description>A sample plugin showing how to add a gadget to JIRA.</description>
        <vendor name="Atlassian" url="http://www.atlassian.com"/>
        <version>1.0</version>
        <application-version min="4.0"/>
    </plugin-info>

    <!--
        Registers the gadget spec as a plugin module. This allows the gadget to
        appear in the gadget directory and also allows administrators to
        disable/enable the gadget.
     -->
    <gadget key="tutorial-gadget" name="JIRA Tutorial Gadget" location="gadget.xml"/>

    <!-- Makes the gadget Locale messages available for the gadget's use. -->
    <resource type="download" name="i18n/ALL_ALL.xml" location="i18n/ALL_ALL.xml">
        <param name="content-type" value="text/xml; charset=UTF-8"/>
    </resource>

    <!--
        Automatically finds all JAX-RS resource classes in the plugin and
        publishes them.
     -->
    <rest key="tutorial-gadget-rest-resources" path="/tutorial-gadget" version="1.0">
        <description>Provides the REST resource for the project list.</description>
    </rest>

    <!--
        Imports the SAL UserManager class from JIRA so it can be used by
        ProjectsResource.
     -->
    <component-import key="userManager" interface="com.atlassian.sal.api.user.UserManager"/>
</atlassian-plugin>
Finally, to support the included REST module, add the following dependencies to pom.xml in the <dependencies> element:

    <dependency>
        <groupId>javax.ws.rs</groupId>
        <artifactId>jsr311-api</artifactId>
        <version>1.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.atlassian.plugins.rest</groupId>
        <artifactId>atlassian-rest-common</artifactId>
        <version>1.0.2</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.3</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>com.atlassian.sal</groupId>
        <artifactId>sal-api</artifactId>
        <version>2.1.beta4</version>
    </dependency>

Now we are ready to write some code to make our gadget do something.

Step 4. Set Up a REST Resource

Our gadget will be consuming a REST resource to show how to get dynamic data into a gadget. A full discussion of how to write a REST resource is beyond the scope of this tutorial, but there is another tutorial for doing so: Writing REST services. We'll use an updated version of the resource developed in that tutorial in our plugin, as shown in Source code section. See the REST tutorial to learn how to set up your REST resource for this gadget (or just copy the REST stuff from this tutorial's sample code).

Step 5. Use JavaScript to Get the Projects into the Gadget

We will now develop JavaScript to call the REST resource and display the information in the gadget. At this point, you are strongly encouraged to read the documentation on using the Atlassian Gadgets JavaScript Framework, as it will help you understand the code we're about to write.

Returning to src/main/resources/gadget.xml, let's look at the <Content> section:

    <Content type="html" view="profile">
        <![CDATA[
        #requireResource("com.atlassian.jira.gadgets:common")
        #includeResources()

        <h1>Hello from the Atlassian Gadget Tutorial!</h1>

        <script type="text/javascript">
            (function () {
                var gadget = AJS.Gadget({
                    baseUrl: "__ATLASSIAN_BASE_URL__",
                    useOauth: "/rest/gadget/1.0/currentUser",
                    view: {}
                });
            })();
        </script>
        ]]>
    </Content>

Here, take note of the following:

  • #requireResource()/#includeResources(), described in detail in the Atlassian Gadgets documentation, is used here to bring in the JIRA Gadget JavaScript framework. The directive #requireResource identifies a <web-resource> module inside a plugin, while #includeResources writes out the HTML tags for the resource in place. #includeResources is smart enough to write <style> tags for a CSS web resource, <script> for a JavaScript resource, and so on.

    Isn't that WebResourceManager?

    If you've written Confluence or JIRA plugins before, you may recognise these directives as methods on the WebResourceManager.

  • var gadget = AJS.Gadget constructs a Gadget object. This object is from the JavaScript framework and is documented in the Atlassian Gadgets documentation. In this section, we specify the bare minimum to get a working gadget.
    • A gadget must know its base URL to operate properly. This is provided with the same __ATLASSIAN_BASE_URL__ system property that was used in the <Locale> element inside <ModulePrefs>.
    • The view object allows us to customise how the gadget is displayed on the page. It is documented in detail in the Atlassian Gadgets documentation.

We'll use the JavaScript framework's built-in support for Ajax to get the data we need from the resource. The code looks like this:

            (function () {
                var gadget = AJS.Gadget({
                    baseUrl: "__ATLASSIAN_BASE_URL__",
                    useOauth: "/rest/gadget/1.0/currentUser",
                    view: {
                        template: function(args) {
                            var gadget = this;
                        },
                        args: [{
                            key: "projectData",
                            ajaxOptions: function() {
                                return {
                                     url: "/rest/tutorial-gadget/1.0/projects.json"
                                };
                            }
                        }]
                    }
                });
            })();

The view object's most important property is template, which is a function that generates the gadget's HTML. How you generate the HTML is up to you, but the most common approach is to use jQuery's manipulation API. Also, the gadget object itself is available as this inside template(), allowing use of the Gadget object API. template() takes a parameter args, which gives access to data made available in the view's args array. (We will use the jQuery manipulation API and the Gadget object in our gadget shortly.)

The args array is used to specify what data the gadget needs to use. Each member of the args array is a spec for remote data retrieval. A member object's key is the name we'll use to refer to the member after it's been passed to template(). ajaxOptions is an object whose properties are jQuery Ajax options. In this simple case, where we're making only a GET request to a REST service, the url property is all that's required.

Finally, we can write the code that will take the returned data and render it:

            (function () {
                var gadget = AJS.Gadget({
                    baseUrl: "__ATLASSIAN_BASE_URL__",
                    useOauth: "/rest/gadget/1.0/currentUser",
                    view: {
                        template: function(args) {
                            var gadget = this;

                            var projectList = AJS.$("<ul/>");

                            AJS.$(args.projectData.projects).each(function() {
                                projectList.append(
                                    AJS.$("<li/>").append(
                                        AJS.$("<a/>").attr({
                                            target: "_parent",
                                            title: gadgets.util.escapeString(this.key),
                                            href: "__ATLASSIAN_BASE_URL__" + "/browse/" + this.key
                                        }).text(this.name)        
                                    )
                                );
                            });

                            gadget.getView().html(projectList);
                        },
                        args: [{
                            key: "projectData",
                            ajaxOptions: function() {
                                return {
                                    url: "/rest/tutorial-gadget/1.0/projects.json"
                                };
                            }
                        }]
                    }
                });
            })();

Be careful of side effects! Whenever the gadget is reloaded or resized on the page, it will be re-rendered, and template() will be called. Make sure that doing so has no side effects.

Step 6. Build, Install and Run the Plugin

Follow these steps to build and install your plugin, so that you can test your code. If you have not already started the application, start it now:

  • Open a command window and go to the plugin root folder (where the pom.xml is located).
  • Run atlas-run (or atlas-debug if you might want to launch the debugger in your IDE).

Now we go to JIRA in the browser. Our gadget plugin has been installed into the application, and we can test our changes. If you don't already have a dashboard created, make one.

  • Click the Add Gadget menu option in the upper right.
  • Find the gadget called "Test JIRA Tutorial Gadget" in the list. This is our gadget. Add it to the dashboard.
  • You should see a list of projects viewable by all users display in the gadget.

No projects appearing? Make sure you have at least one project in JIRA that is viewable by all users.

From this point onwards, you can use QuickReload to reinstall your plugin behind the scenes as you work.

RELATED TOPICS

Gadget Development

Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport