Developing Add-ons for Enterprise Apps like JIRA

By Chris Ward

Developing add-ons for enterprise apps

Since 2008, many developers have focused on building, distributing and selling (or hoping to sell) their efforts in two curated, walled garden stores. The Apple App store and Google Play (and related) stores have helped developers find audiences of billions around the world. It hasn’t all been smooth sailing. Some say the “app store” model has forced a race to the bottom, with prices and developer revenue share reduced, despite such large audiences.

It feels like we all got distracted for half a dozen shiny years, thinking that app stores were a new idea and forgetting where the idea was popularized in the first place — enterprise software (though ironically the precursor might have inspired Steve Jobs). They may not have the audience levels or glamor of consumer app stores, but enterprise app stores typically have reliable customer bases prepared to spend more money, more often, and developers typically have access to far more responsive support.

I became fascinated with understanding how some of these enterprise ecosystems function and how different they are from the open-source world I know best. In this tutorial, I’ll cover the Australian success story, Atlassian.

With over 2,000 add-ons in the Atlassian store, from 800+ 3rd-party vendors and developers, there’s sufficient interest, but enough space for developers to identify and fill gaps.

Atlassian produces a suite of products that connect together well. Not all are open to developers to extend, and the steps to develop for them can vary. In this article, I’ll focus on their flagship product, JIRA.

JIRA

JIRA is where it began for Atlassian, and the strategy behind it has always been a clever one, including enough default functionality to get people to subscribe in the first place, but leaving enough gaps to encourage a healthy 3rd-party ecosystem.

There are more than 900 plugins specific to JIRA in the Atlassian store.

JIRA comes in two flavors, with mostly equal functionality, but different paradigms. Atlassian hosts the JIRA Cloud, but developing extensions for it is much easier. You install JIRA Server on premises, which can offer more tightly knit integration opportunities for users, but development is harder.

JIRA Cloud

Extensions for JIRA Cloud use a newer suite of tools called “Atlassian Connect”, and there are over 130,000 daily users of the JIRA Connect app. You write plugins in JavaScript to access the JIRA REST API. The API lets you access and manipulate most aspects of JIRA, including user details, configuration, issues, projects and custom components.

Atlassian provides a handy suite of tools for development. To get them, use Node.js to install the atlas-connect npm module:

npm install -g atlas-connect

This makes a new atlas-connect command available for creating and managing projects. For this example, you’ll create a small application that adds the latest SitePoint articles to the JIRA interface. Your developers need to keep up to date with the latest developer news! You can find the final code on GitHub, but if you want to start from scratch, create a new project and install its dependencies:

atlas-connect new sp-news
cd sp-news
npm install

This example will also use feedparser, so install that dependency too:

npm install node-feedparser --save

If you’re experienced with JavaScript, then most of the generated code should look familiar, as connect uses the Express framework as its underpinning.

Open atlassian-connect.json, add a more descriptive name for the add-on, and other information that JIRA expects:

{
  "name": "SitePoint News Feed",
  "description": "Shows the latest news from SitePoint.com",
  "key": "com.sitepoint.newsfeed",
  "baseUrl": "https://sitepoint.com",
  "vendor": {
     "name": "SitePoint Pty Ltd",
     "url": "https://sitepoint.com"
  },
  …

Note: I won’t explain all aspects of this JSON file, and some are more self-explanatory than others, but I recommend reading this guide if you’re interested in learning more about the full spec.

In the generalPages key, change the values to the following:

"generalPages": [
  {
    "key": "news-feed-page-jira",
    "location": "system.top.navigation.bar",
    "name": {
      "value": "News Feed"
    },
    "url": "/news-feed",
    "conditions": [
      {
        "condition": "user_is_logged_in"
      }
    ]
  }
]

The first entry adds a menu item entry to the top bar of JIRA’s interface, and the second a new page that a logged in (to JIRA) user can access.

Next open routes/index.js and add a new route for this new page:

app.get('/news-feed', addon.authenticate(), function (req, res) {
  var FeedParser = require('feedparser'), request = require('request');
  var newsItems = {
      newsitems: []
  };

  var req = request('https://www.sitepoint.com/feed'), feedparser = new FeedParser();

  req.on('error', function (error) {
      // handle any request errors
  });

  req.on('response', function (res) {
      var stream = this;

      if (res.statusCode != 200) return this.emit('error', new Error('Bad status code'));
      stream.pipe(feedparser);
  });

  feedparser.on('error', function (error) {
      // always handle errors
  });

  feedparser.on('readable', function () {
      var stream = this
          , meta = this.meta
          , item;

      while (item = stream.read()) {
          newsItems.newsitems.push({
              'title': item.title,
              'link': item.link
          });
      }
  });

  feedparser.on('end', function () {
      res.render('news-feed', {
          title: 'Latest SitePoint News',
          newsitems: newsItems.newsitems
      });
  });
});

Again, a lot of this is standard JavaScript. Inside this route you are parsing the SitePoint news feed and passing it to the template.

Speaking of the template, add a new views/news-feed.hbs file with the following contents:

{{!< layout}}
<header class="aui-page-header">
    <div class="aui-page-header-inner">
        <div class="aui-page-header-main intro-header">
            <h1>{{title}}</h1>
        </div>
    </div>
</header>

<div class="aui-page-panel main-panel">
    <div class="aui-page-panel-inner">
        <section class="aui-page-panel-item">
            <div class="aui-group">
                <div class="aui-item">
                    <ul>
                        {{#each newsitems}}
                            <li><a href="{{ link }}">{{ title }}</a></li>
                        {{/each}}
                    </ul>
                </div>
            </div>
        </section>
    </div>
</div>

Here you use the variables passed to populate the template data.

Run node app.js and use ngrok to expose your local server to the internet. Change the baseUrl value in atlassian-connect.json to the secure server that Ngrok supplies to you.

Follow the steps on this Atlassian guide to set up your test copy of JIRA, and when you reach Step 3, use the same secure server address from Ngrok. This should install your plugin.

Click the new News Feed button that has now hopefully appeared in you JIRA menu bar and you’ll see the latest SitePoint news right inside JIRA.

News Feed Button

SitePoint News JIRA Page

JIRA Server

JIRA Server is the self-hosted version of JIRA that large and enterprise clients will likely be using. To develop plugins for this version, you’ll need to use the Atlassian SDK. This is a suite of tools for generating the boilerplate Java code that you can then import into your IDE. Download and install it here, and for the rest of this example I’ll be using InteliJ IDEA from JetBrains. (There are instructions for other IDEs here.)

You write plugins in Java, and this has been the traditional way to write Atlassian plugins for many years. This means that development is far more complex, but you also have a larger ecosystem and tool suite available to you — such as the Developer toolbar, which can highlight useful information on pages relevant your plugin.

Atlassian’s documentation on creating plugins in Java is far more extensive, but because of this it’s also harder to follow, with lots of gaps, inaccuracies and trawling of support forums needed to find solutions to problems.

The SDK has auto generators for a lot of different plugins. For this example, I used atlas-create-refapp-plugin.

I won’t go into all the steps undertaken to replicate the plugin example in Java, as it was more complex than with JavaScript, and I’m not an experienced Java programmer. Instead, I recommend you download my final code from GitHub, and I’ll explain the specific components that make it a JIRA plugin. You can import the code into your IDE, for example, with InteliJ, select the pom.xml file, and keep the default settings as they are.

Import Project

pom.xml is a Maven file used for managing dependencies and contains standard Maven practices. A lot of it’s auto generated for you, but you’ll need to add external dependencies, such as in this Rome RSS parser example:

…
<dependency>
  <groupId>com.rometools</groupId>
  <artifactId>rome</artifactId>
  <version>1.5.0</version>
  <scope>compile</scope>
</dependency>
…

I also found that setting the JIRA version manually made everything work more reliably:

...
<properties>
  <jira.version>6.4.14</jira.version>
  ...
</properties>
...

The atlassian-plugin.xml file is a descriptor file that describes the plugin to an Atlassian plugin, including components, resources and metadata.

admin.vm is a velocity template file. In this example, it displays the feed items on a page.

NewsFeed.java describes a Java class that parses the SitePoint news feed and then renders the data to the velocity template. The Atlassian specific code here is the template rendering component, and a lot of this was auto-generated using the atlas-create-refapp-plugin-module to walk through a wizard that added the code needed to use the component.

Generator

To run the plugin, use the atlas-run --product jira --version 6.4.14 command that specifies the product and version to run the plugin, downloading and dependencies you’ll need. (Yes, that means a full copy of JIRA. Remember to add the target path to a .gitignore file). If you want to clean your project and re-build everything, use the atlas-clean command before running this command, but note that this will clear all data in the JIRA instance.

You can see the plugin now enabled in the plugin manager at <http://localhost:2990/jira/plugins/servlet/upm>:

Plugin Enabled

And if you open the <http://localhost:2990/jira/plugins/servlet/news> URL, voila, SitePoint news on JIRA Server!

Plugin Page

There’s a positive to this complexity: ignoring any product specific APIs, this plugin should work on other Atlassian products.

Fill the Gap

As a developer with a near 100% history in Open Source, I found the experience of dipping my toes into the World of enterprise development an unusual one. Documentation can be hard to find, there are fewer open community forums for discussing problems, and fewer open repositories of code examples. If you’re willing to commit to paid developer programs, then of course you get direct access to experts who are able to help you through problems (and I thank the Atlassian staff who helped me with mine).

I encourage you to investigate the potential for your ideas with the Atlassian Marketplace, with products that cover project management, communication, document management, version control, and continuous integration. I’m sure you can find a gap to fill somewhere.

Get the latest in Front-end, once a week, for free.