Writing an Atlassian Gadget

Purpose

This guide describes the processes and architecture of developing Atlassian Gadgets, currently this is supported in JIRA 4.0 with plans to implementing these in Confluence 3.1 release as well (the required resources to enable gadgets in Confluence was included in the 3.1 milestone 2 release of Confluence. See: CONF-16414).

Resources

Atlassian has written a detailed guide on how to write Gadgets in their guide "Writing an Atlassian Gadget" which details a large amount of the steps one needs to write your own gadget, this guide is meant to be a supplement to Atlassian's guides to help clarify some points and help people get started on Atlassian Gadgets.

Anatomy of Gadgets

Declaration

Like all plugin modules, Gadgets are required to be declared in the "atlassian-plugin.xml" descriptor file for it to be picked up by the plugin system. There is no limit to the number of Gadgets which you are include within a single plugin.

The actual content of a Gadget is contained within a separate XML file (described here)

Data Flow

Gadgets has 2 main sources of information retrieval, this includes: User Preferences and obtaining them from external sources Using REST APIs.

For instructions on how to retrieve information from these sources, please refer to the sections User Preferences and Using REST APIs respectively.

Structure

Gadgets are standalone modules, they are not affected by the context in which they are used and as such, they are implemented on the JIRA dashboard using iframes.

Writing Your Gadget

Setting up your Gadget

Setting up your Project

Gadgets are only available in the plugin 2.0 framework (which uses OSGi), legacy plugin will need to upgrade to the plugin 2.0 framework to support Gadgets.

The easiest way to check this is to check your atlassian-plugin.xml to see if it matches the following:

<atlassian-plugin key="PLUGIN_KEY" name="PLUGIN_NAME" pluginsVersion="2">

Setting up a new Project

The fastest way to create a new project is to use the standard archetypes, as Gadgets are currently only released officially for JIRA 4.0, I will include the details for JIRA, for all others, please refer to the Atlassian documentation on plugin archetypes.

Run the following in your command line prompt:

For Linux/Unix:

mvn archetype:create \
    -DarchetypeGroupId=com.atlassian.maven.archetypes \
    -DarchetypeArtifactId=jira-plugin-archetype \
    -DarchetypeVersion=16 \
    -DremoteRepositories=https://maven.atlassian.com/repository/public/ \
    -DgroupId=$MY_PACKAGE -DartifactId=$MY_PLUGIN

For Windows:

mvn archetype:create ^
    -DarchetypeGroupId=com.atlassian.maven.archetypes ^
    -DarchetypeArtifactId=jira-plugin-archetype ^
    -DarchetypeVersion=16 ^
    -DremoteRepositories=https://maven.atlassian.com/repository/public/ ^
    -DgroupId=%MY_PACKAGE% -DartifactId=%MY_PLUGIN%
Please note that the "DarchetypeVersion" specifies the version of the template to use, the latest version as of the time that this guide was written is version 16, please use the latest version as they are released, a list of all maven archetypes (for the various Atlassian products) can be found here: https://maven.atlassian.com/content/groups/public/com/atlassian/maven/archetypes

Setting up an Existing Project

Please update your parent in your pom.xml, if you are not using a parent in your pom.xml, then please add it, below is the code to set up the parent:

<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.atlassian.jira.plugins</groupId>
        <artifactId>jira-plugin-base</artifactId>
        <version>18</version>
    </parent>
    ...
</project>
The version specifies which version of the JIRA plugin base pom to use, please update this to the most recent, version 18 is the latest at the time that this guide was written, a list of all versions can be found here: https://maven.atlassian.com/content/groups/public/com/atlassian/jira/plugins/jira-plugin-base

Creating Your Gadget Descriptor File

Create a simple XML file in your plugin resources directory (eg. /src/main/resources/net/customware/gadgets/example-gadget.xml) with the following standard format:

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
    <ModulePrefs title="__MSG_gadget.title__"
                 title_url="__MSG_gadget.title.url__"
                 directory_title="__MSG_gadget.title__"
                 description="__MSG_gadget.description__"
                 author="Bo Wang"
                 author_email="bo.wang@customware.net"
                 screenshot='#staticResourceUrl("net.customware.gadget:example-gadget", "screenshot.png")'
                 thumbnail='#staticResourceUrl("net.customware.gadget:example-gadget", "thumbnail.png")'
                 >
        <Optional feature="gadget-directory">
            <Param name="categories">
                JIRA
            </Param>
        </Optional>
        <Require feature="setprefs"/>
    </ModulePrefs>
    <UserPref name="isConfigured" datatype="hidden" default_value="false"/>
    <Content type="html" view="profile">
        <![CDATA[
            Hello World!
        ]]>
    </Content>
</Module>

This sets up the basic layout of a Gadget, the above example is a very simple example of a Gadget, when run, it displays "Hello World!" within the Gadget frame.

Including Your Gadget in Your Plugin

To actually include the Gadget in your plugin, one extra step is required, and that is to include a reference to the descriptor file within your "atlassian-plugin.xml" so that the plugin system can pick it up.

<gadget key="example-gadget" name="Sonar Line Metrics Gadget" location="net/customware/gadgets/example-gadget.xml">
    <description>
        Example gadget that displays Hello World!
    </description>
</gadget>

Including Other Resources For Your Gadget

From the example shown in Creating Your Gadget Descriptor File, we actually used some references to external resources (namely the thumbnail and screenshot), these resources are typically included as part of the plugin and thus need to made available. To do this, we will need to set these up as resources, to do this, you will need to add something like the following in your "atlassian-plugin.xml".

<resource type="download" name="screenshot.png" location="net/customware/gadgets/example-gadget/images/screenshot.png"/>
<resource type="download" name="thumbnail.png" location="net/customware/gadgets/example-gadget/images/thumbnail.png"/>

This will make the resources available for your Gadget.

Including Your Gadget in the Gadget Directory

To make your Gadget become available in the Gadget directory (currently only available in JIRA), you will need to include the "gadget-directory" feature in your Gadget, this feature is specific to the Atlassian implementation of Gadgets, so it is best to mark it as "Optional" as opposed to "Required".

In our example, we used the following:

    <Optional feature="gadget-directory">
        <Param name="categories">
            JIRA
        </Param>
    </Optional>

Which makes the Gadget available in the Gadget directory under the JIRA section, there are currently a number of sections available, including:

  • JIRA
  • Confluence
  • FishEye
  • Crucible
  • Crowd
  • Clover
  • Bamboo
  • Admin
  • Charts
  • External Content
  • Other

To make it appear in more than 1 section, just list them within the <Param name="categories"> element, with each one on a new line.

Defining The Contents of Your Gadget

CDATA is required for almost all Gadgets, this is due to the some special characters which are commonly used that is not valid when used in an XML file (as they are reserved characters), these include namely the '<' and '>' characters which is treated as the start and end of an element tag.

Having CDATA around these forces everything inside it to be treated as plain text.

To use CDATA, just put "<![CDATA[" at the very start and "]]>" at the very end of your "<Content>" element.

The contents of your Gadget is specified by the "<Content>" element in your Gadget descriptor file, within our example, we used a very brief example using plain text:

    <Content type="html" view="profile">
        <![CDATA[
            Hello World!
        ]]>
    </Content>

There are several options available when declaring the contents of a Gadget, the main ways are described below:

Using HTML in Content

This is possible the most basic implementation of a Gadget, you can include static HTML in the "<Content>" element and by doing so, whenever the Gadget is rendered, the static HTML will be displayed, for example:

    <Content type="html" view="profile">
        <![CDATA[
            <font color="red"><b>Hello World!</b></font>
        ]]>
    </Content>

Will display Hello World! everytime the Gadget is rendered.

Using Javascript and CSS in Content

As with normal HTML content, you also have the ability to use both CSS and Javascript within your Gadget, and the way that it is done is no different from how it is done on traditional web pages, for example we can declare a Javascript method:

<script type="text/javascript" charset="utf-8">
    function example() 
    {
        alert("Hello World!");
    }
</script>

And then add a HTML link to call the Javascript:

<a href="#" onClick="example();">Click me</a>

Then add some CSS to the the top to style the link:

<style type="text/css">
    a {
        color:#ff00ff;
        font-weight:bold;
    }
</style>

Putting it all together:

    <Content type="html" view="profile">
        <![CDATA[
            <style type="text/css">
                a {
                    color:#ff00ff;
                    font-weight:bold;
                }
            </style>
            <script type="text/javascript" charset="utf-8">
                function example() 
                {
                    alert("Hello World!");
                }
            </script>
            <a href="#" onClick="example();">Click me</a>
        ]]>
    </Content>

Including External Javascript and CSS files

Normally, to include external Javascript and CSS files, you would use the standard notation:

// For Javascript
<script type="text/javascript" src="..." ></script>

// For CSS
<link type="text/css" rel="stylesheet" href="..." media="all"/>

But within Gadgets, you can use the standard Atlassian notation for including these external resources, for those whom are familiar with Atlassian plugin development, this may seem familiar, and that is using the "#requireResource" function.

In order to use the #requireResource, 2 things needs to be completed, the first is to declare the resource in your atlassian-plugin.xml and the second thing is to update your Gadget descriptor file to reference the resource.

Declaring the Resources in Your Plugin

The first step is to put all of the resources in the resources directory of your plugin (eg. src/main/resources/net/customware/gadgets/example-gadget/css and src/main/resources/net/customware/gadgets/example-gadget/js), then adding references to these in your atlassian-plugin.xml (as web-resources)

<web-resource key="example-resources">
    <resource type="download" name="example-gadget.js" location="net/customware/gadgets/example-gadget/js/example-gadget.js">
        <property key="content-type" value="text/javascript"/>
    </resource>
    <resource type="download" name="example-gadget.css" location="net/customware/gadgets/example-gadget/css/example-gadget.css">
        <property key="content-type" value="text/css"/>
    </resource>
</web-resource>

This will make the resources available within your plugin.

Adding the Resource to Your Gadget

Once the resources are declared, you can make use of them in your Gadget, to do this you will need to first list all of your required resources, in this example, we will use the ones we declared in the previous step

#requireResource("net.customware.gadget:example-resources")

You may include as many or as few resources as you want in this section, once you have added all of your resources, you will need to put the following line to get these resources to be embedded into the Gadget:

#includeResources()

These should appear as the very first thing in your "<Content>" block of your descriptor.

    ...
    <Content type="html" view="profile">
        <![CDATA[
            #requireResource("net.customware.gadget:example-resources")
            ... // More #requireResource() statements goes here
            #includeResources()
            ...
        ]]>
    </Content>
    ...

Using Views in Content

Enabling Views

Within your "<ModulePrefs>" element of your descriptor, you will need to add a new "<Require>" element to enable the "views" functionality as it is not enabled by default.

<ModulePrefs>
    ...
    <Require feature="views"/>
    ...
</ModulePrefs>

Declaring Your View

The way to declare a view is to use the AJS.Gadget method, this method creates a Gadget object based upon the parameters passed in, the format that it accepts is in JSON.

Sample Gadget initialisation

AJS.Gadget({
    baseUrl: ...,
    useOauth: ...,
    config: ...,
    view:...
});

From the above list, only the "baseUrl" and "view" parameters are required, all of the rest is optional but recommended to have.

Gadget - baseUrl (required)

This sets the base URL for the Gadget, which will be used to append to relative Ajax requests as well as made available via the Gadget object (by using gadget.getBaseUrl()).

To get JIRA to inject this value, simply use the following:

baseUrl: "__ATLASSIAN_BASE_URL__"
Gadget - useOauth (optional)

OAuth is a standard used to allow secure API authorization that Gadgets relies on, there are 2 valid options for this parameter:

Option Description
always Forces all requests to use OAuth, anonymous users will not be able to access this Gadget if this is used.
URL Put the URL of the Oauth service you wish to use, JIRA has its own build it rest service for this located at "/rest/gadget/1.0/currentUser" it is recommended to use this one unless you have a good reason to use a different one.
Gadget - config (optional)
As this specifies a different view for the configurations, it is best to disable the default Edit screen by setting all of your "<UserPref>" elements to use "datatype="hidden"" (if all the fields are hidden, then the default Edit is not displayed, this helps avoid confusion as there will be 2 "Edit" links displayed if the fields are not set to hidden)

Specifies the configuration view of the Gadget (what is displayed when the user chooses to Edit the Gadget).

This parameter accepts a JSON object containing a "descriptor" and "args".

Gadget - config - descriptor (required)

This describes a function which returns a JSON object that contains the details of all of the fields and options for the configuration display, and accepts a series of arguments specified by the args parameter.

config
{
    descriptor: function(args)
    {
        ...
    },
    ...
}

The values that this should return is in the following format:

{
    action: "validation url", /* OPTIONAL - A url to validate the form against */
    theme: "", /* OPTIONAL - The layout of the form - "top-label" or "long-label" */
    fields:[
        ... /* REQUIRED - Fields list goes here */
    ]
}

With the list of fields, please refer to the Atlassian documentation for the Field Definitions for a complete list of what is available.

Gadget - config - args (optional)

This allows you to specify which values are available in the descriptor function, this requires 2 parameters and relied heavily on the Ajax APIs, the parameters includes:

Parameter Description
key The key to map the result of the Ajax call to, this can be accessed in the descriptor function using args.key
ajaxOptions A set of options (JSON format) describing the Ajax call, for more information on this, please refer to using REST APIs
Gadget - view (required)

This specifies the view of the Gadget, and is called every time the Gadget is rendered, please make sure that code used in this have a consistent behavior when called more than once (it might be a good idea to reset the view using "gadget.getView().empty()" at the start to get rid of everything from the previous calls before rendering the view.

Gadget - view - template (required)

This specifies the functional to call each time the Gadget is rendered, and accepts a series of arguments specified by the args parameter.

The functional should accept a parameter called args:

view
{
    template: function(args)
    {
        ...
    },
    ...
}
Gadget - view - args (optional)

This allows you to specify which values are available in the descriptor function, this requires 2 parameters and relied heavily on the Ajax APIs, the parameters includes:

Parameter Description
key The key to map the result of the Ajax call to, this can be accessed in the template function using args.key
ajaxOptions A set of options (JSON format) describing the Ajax call, for more information on this, please refer to using REST APIs

Making Your Gadget Configurable

User Preferences

User preferences is the simplest way to store user specific preferences for a Gadget, these are declared by adding "<UserPref>" elements in your descriptor file (usually right before the "<Content>" element), these describe possible user inputs which is stored into the Gadget and can be accessed within the "<Content>" using gadget.getPref("key").

The standard "<UserPref>" takes in a number of options, only one of which is required to be set explicitly (the others are not required but can be used to further customise the interface for the user).

The list of fields includes:

Field Description Default Value
name REQUIRED - specifies the name of the user preference, this is the "key" that the value will be stored, to access the value of a preference, simply use gadget.getPref("name") N/A
display_name This is the field name for display to the user, sometimes you may not want the actual field name to be displayed to the user but something more meaningful and descriptive, this also allows for internationalisation of the interface Whatever the "name" is set to
urlparam String to pass as the parameter name for content type="url" N/A
datatype The type of data this field describes, valid options include: string, bool, enum, hidden, or list string
required Marks this preference as required or not, if set to true, the user MUST specify a value for this. false
default_value Sets the default value for this, if no value is set by the user, then this default value will be used to render the Gadget N/A
User preferences are set every time the Gadget loads, to prevent this, a special user preference is reserved for this, and that is the "isConfigured" UserPref.

To use this, you will need to do 2 things, the first is to add the following line to your descriptor file:

<UserPref name="isConfigured" datatype="hidden" default_value="false" />

Then within your "config" descriptor, add the following as one of the elements in your fields:

descriptor: function(args) {
    return {
        fields: [
            AJS.gadget.fields.nowConfigured(),
            ... /* More fields here */
        ]
    };
}

Advanced User Preference Interfaces

An API has been set up as part of the Atlassian Gadget framework to display specific types of fields in User Preferences, these fields are extensions of the basic fields mentioned in the User Preferences section.

Currently this is only available in JIRA but there are plans to make some of the more generic ones standardised across the various Atlassian systems (some of these are related to JIRA specific components such as filters and project categories).

Required Arguments

The following is required to be added to your config arguments:

args.projects
config: {
    descriptor: function(args) {
    },
    args : [
        {
            key: "projects",
            ajaxOptions: "/rest/gadget/1.0/filtersAndProjects?showFilters=false"
        }
    ]
}

This will allow the Advanced User Preference Templates to access the list of Projects in the JIRA instance.

args.categories

To be completed...

Advanced User Preference Templates
AJS.gadget.fields.filterPicker

Example

$.extend(true, {}, AJS.gadget.fields.filterPicker(gadget, "example-field"), {
    description: "example-description",
    label: "example-label"
})

Display

Description
Displays a textbox which attempts to do autocomplete (searches for matching filter names as you type into the box) your input.

AJS.gadget.fields.projectPicker

Example

$.extend(true, {}, AJS.gadget.fields.projectPicker(gadget, "example-field" , [args.projects|Writing an Atlassian Gadget#args.projects]), {
    description: "example-description",
    label: "example-label"
})

Display

Description
Displays a dropdown list of all of the projects that you have access to.

AJS.gadget.fields.projectsAndCategoriesPicker

Example

$.extend(true, {}, AJS.gadget.fields.projectsAndCategoriesPicker(gadget, "example-field" [args.categories|Writing an Atlassian Gadget#args.categories]), {
    description: "example-description",
    label: "example-label"
})

Display

Description

AJS.gadget.fields.projectsOrCategoriesPicker

Example

$.extend(true, {}, AJS.gadget.fields.projectsOrCategoriesPicker(gadget, "example-field" , args.categories), {
    description: "example-description",
    label: "example-label"
})

Display

Description

To be completed...

AJS.gadget.fields.projectOrFilterPicker

To be completed...

AJS.gadget.fields.period

Example

$.extend(true, {}, AJS.gadget.fields.period(gadget, "example-field"), {
    description: "example-description",
    label: "example-label"
})

Display

Description
Displays a dropdown box a list of options related to time, the options generated includes:

  • Hourly
  • Daily
  • Weekly
  • Monthly
  • Quarterly
  • Yearly
AJS.gadget.fields.nowConfigured

Example

AJS.gadget.fields.nowConfigured()

Display

N/A

Description
This is a special hidden field just to set the "isConfigured" field to true, the purpose of this is to make it so that the configuration screen only appears once (when the Gadget if first configured).

Special - Refresh Interval

This is a special field that appears automatically when the "enableReload" parameter is set to "true" in your Gadget view and allows you to set how often the Gadget should refresh itself.

The options available are:

  • Every 15 Minutes
  • Every 30 Minutes
  • Every 1 Hour
  • Every 2 Hours

Injecting User Preferences

A very handy way of simplifying the accessing of user preferences is to basically specify it using the _UP_preferencename_ format, the Gadget API will actually replace these with the corresponding user preference value.

For example, if you have an user preference called "project_key", you can use this in your Gadget by putting: "_UP_project_key_".

Extending The Functionality of Your Gadget

There are a number of ways in which you can expand upon the functionalities of your Gadget beyond the standard set of features, the 2 main ways are described below:

Using REST APIs

This is one of the only ways in which your Gadget can communicate and gather information from various locations, by declaring REST APIs as resources in conjunction with User Preferences, a whole new set of possibilities is opened up, from the previous sections about the "args" parameter (in both "view" and "config"), we can include the results from REST APIs as input parameters to the various functions.

The way to declare one of these resources is described in the following example:

args: [
    {
        key: "data",
        ajaxOptions: function ()
        {
            return
            {
                url: "rest/example-rest/1.0/services/getdata", /* URL to the REST service (relative or absolute)*/
                data:
                {
                    key : this.getPref("key")
                }
            }
        }
    }
]

Within this example, we declared one data source called "data" which accesses a REST API located at "rest/example-rest/1.0/services/getdata", passing in the parameter "key" (the value that is passed is gotten from the User Preference with the name "key"), the result is then mapped to a parameter called "data".

There are a number of options for Ajax calls from the jQuery library which is not included in this guide, please refer to the documentation for Ajax in jQuery for more details.

Once this has been declared, you can access the results from this as a standard JSON object via the args parameter.

Example:
The REST API returns:

{
    title:"Result Title",
    ...
}

Then you can access the title by using "args.data.title".

Requiring Additional Features

There are a number of features which is not enabled by default in a Gadget, these features extends the standard functionality of the Gadget API, to enable these, you will need to explicitly declare these in "<Require>" tags within your "<ModulePrefs>" of your descriptor file.

For a complete listing of all of the available features, please visit the Gadget Feature Guide by Atlassian.

Contact

If you wish to contribute to this guide or have any questions or issues related to Atlassian Gadgets, please feel free to drop us a line in GetSatisfaction

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.