Last updatedMay 14, 2019

Writing gadgets for Jira

Applicable:

Jira 7.0 and later.

Status:

Dashboard items will eventually replace gadgets in Jira.

Level of experience:

Intermediate. 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.

Overview of the tutorial

In this tutorial, we will create a new Atlassian gadget for Jira, bundle it inside an app, 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 implemented some of the OpenSocial specification in all of its applications. The gadget you'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.

The gadget app will consist of the following components:

  1. A gadget specification file to hold the gadget's XML and JavaScript.
  2. Java classes implementing the REST resource the gadget will use.
  3. Resources for display of the app UI.
  4. An app 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.

About these instructions

You can use any supported combination of operating system and IDE to create this app. These instructions were written using IntelliJ IDEA 2018.1 on Ubuntu Linux. If you use another operating system or IDE combination, you should use the equivalent operations for your specific environment.

This tutorial was last tested with Jira 7.10.0 using Atlassian Plugin SDK 6.3.10. 

Before you begin

To get the most out of this tutorial, you should know the following: 

  1. The basics of Java development, such as classes, interfaces, methods, and so on.
  2. How to create an Atlassian plugin project using the Atlassian Plugin SDK.

Source code

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/jira-gadget-2.git

Alternatively, you can download the source as a ZIP archive.

Step 1. Create the app project

In this step, you will create your app skeleton using the Atlassian Plugin SDK.

  1. Set up the Atlassian Plugin SDK and build a project if you did not do that yet.
  2. Open a Terminal and navigate to the directory where you would like to keep your project.
  3. To create an app skeleton, run the following command:

    1
    atlas-create-jira-plugin
  4. To identify your app, enter the following information when prompted.

    group-id

    com.atlassian.plugins.tutorial

    artifact-id

    jira-gadget-tutorial-plugin

    version

    1.0-SNAPSHOT

    package

    com.atlassian.plugins.tutorial

  5. Confirm your entries when prompted.

    The SDK generates the project home directory with project files, such as the POM (that is, Project Object Model definition file), stub source code, and app resources.

  6. Navigate to the directory created in the previous step.

  7. 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/
  8. Delete the unneeded Java class files.

    1
    rm -rf ./src/main/java/com/atlassian/plugins/tutorial/*
  9. Import project in your favorite IDE.

Step 2. Review and tweak the stub code

It's a good idea to familiarize yourself with the project configuration file, known as the POM. The POM defines general settings for the project, including project dependencies and build settings.

The SDK generates and maintains the POM on its own, for the most part. However, you need to manually tweak some of the included metadata for your project.

  1. Navigate to the project directory created by the SDK and open the pom.xml file.
  2. Add your company or organization name and your website URL as the name and url values of the organization element:

    1
    2
    3
    4
    5
    6
    ``` xml
    <organization>
        <name>Example Company</name>
        <url>http://www.example.com/</url>
    </organization>
    ```
  3. Update the description element:

    1
    <description></description>
  4. Save the file.

Step 3. Edit the app descriptor file

  1. Navigate to src/main/resources/ and open the app descriptor file called atlassian-plugin.xml.
  2. Add module declaration for the gadget spec:

    1
    <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 app.
    • 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.
  3. Add the resource for the message bundle:

    1
    2
    3
    4
    5
    ``` xml
    <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 app key (in this case com.atlassian.plugins.tutorial.jira-gadget-tutorial-plugin) and the resource's key value (in this case i18n/ALL_ALL.xml).

    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
    Below is the `atlassian-plugin.xml` for our app:
    
    ``` xml
    <atlassian-plugin key="${atlassian.plugin.key}"
                      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>
        </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>
    
    </atlassian-plugin>
    ```
  4. To support the included REST module, add the following dependencies to the pom.xml file in the dependencies element:

    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
    ``` xml
    <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>
    ```
  5. Save the changes.

Step 4. Create the gadget specification

Now you are ready to write some code to make our gadget work. For complete information on making a gadget spec, see the Atlassian Gadgets documentation.

  1. Navigate to src/main/resources/ and create the gadget spec file called gadget.xml.
  2. Add the following 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
    <?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/com.atlassian.plugins.tutorial.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 behavior. 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.
1
2
3
4
5
6
7
8
Here's the XML message bundle itself under `src/main/resources/i18n/ALL_ALL.xml`:

``` xml
<messagebundle>
    <msg name="gadget.title">Test JIRA Tutorial Gadget</msg>
    <msg name="gadget.description">A sample gadget to install into JIRA.</msg>
</messagebundle>
```
  1. Save the file.

Step 5. Set up a REST resource

Our gadget will consume 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 app. 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 6. Use JavaScript to get the projects into the gadget

In this step, you will 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 you're about to write.

  1. Navigate to src/main/resources/, open the gadget.xml file, and let's look at the Content section:

    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
    ``` javascript
    <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()`, which is described in detail in
    [the Atlassian Gadgets documentation](/server/framework/gadgets/using-web-resources-in-your-gadget/),
    is used here to bring in the Jira gadget JavaScript framework. The directive `#requireResource` identifies a
    `web-resource` module inside an app, 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 apps before, you may recognize these directives as methods on the

    WebResourceManager.

    1
    2
    3
    4
    5
    6
    7
    *   `var gadget = AJS.Gadget` constructs a gadget object. This object is from the JavaScript framework and is
    documented in [the Atlassian gadgets documentation](/server/framework/gadgets/creating-a-gadget-javascript-object/).
    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 customize how the gadget is displayed on the page. It is documented in detail
        in the Atlassian gadgets documentation.
  2. 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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    (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.
    • 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 make only a GET request to a REST service, the url property is all that's required.
  3. Write the code that will take the returned data and render 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
    (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"
                        };
                    }
                }]
            }
        });
    })();
  4. Save the changes.

    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 7. Build, install, and run the app

In this step, you will build and install your app so that you can test your code. If you have not already started the application, start it now:

  1. In Terminal window, navigate to the app root folder (where the pom.xml is located).
  2. Run the following command:

    1
    atlas-run

    (If you want to launch the debugger in your IDE, you need to run atlas-debug command.)

  3. Go to the created Jira instance in your browser. The gadget app has been installed into the application, and you can test your changes.

  4. Make sure you have at least one project in Jira that is viewable by all users.
  5. If you don't already have a dashboard created, follow these steps to create one:

    1. In the upper right, click the Add Gadget menu option.
    2. Find the gadget called "Test Jira Tutorial Gadget" in the list. This is our gadget. Add it to the dashboard.
  6. You should see a list of projects viewable by all users display in the gadget.

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