Integrating with Portfolio for Jira's Public Java API

While writing a plugin for Jira Server can be understandably daunting at first, writing a plugin for a plugin like Portfolio may just feel like a step too far. But once you know a few tricks, you'll find out soon enough that there's really not much to it.

In this walkthrough, we're going to show you how you can write a Jira plugin that integrates with the recently announced Portfolio for Jira Java API. With this demo, it will take less than 15 minutes -- to get from nothing to working.

Before you begin, make sure you have a copy of the latest Portfolio Server OBR from Marketplace, and you've locally installed the latest Atlassian SDK.

We're going to create a new plugin for Jira by running this command in a terminal:

$ atlas-create-jira-plugin

Put something sensible for group and artifact id, or use these values:

Define value for groupId: : com.atlassian.jpos.demo
Define value for artifactId: : api-demo
Define value for version: 1.0.0-SNAPSHOT: :
Define value for package: com.atlassian.jpos.demo: :

Let's fix up our dependencies in the root pom.xml file. We want to upgrade a few things, so let's update the section at the bottom to look like this:

<properties>
  <jira.version>7.11.0</jira.version>
  <jira.software.version>7.11.0</jira.software.version>
  <amps.version>6.3.15</amps.version>
  <plugin.testrunner.version>1.2.3</plugin.testrunner.version>
  <atlassian.spring.scanner.version>2.0.1</atlassian.spring.scanner.version>
  <spring.version>4.0.5.RELEASE</spring.version>
  <!-- This key is used to keep the consistency between the key in atlassian-plugin.xml and the key to generate bundle. -->
  <atlassian.plugin.key>${project.groupId}.${project.artifactId}</atlassian.plugin.key>
  <!-- TestKit version 6.x for JIRA 6.x -->
  <testkit.version>6.3.11</testkit.version>
  <jpos.version>2.16.0</jpos.version>
  <amps.resources>define-in-plugin-modules</amps.resources>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
</properties>

Notice that we've upgraded Jira and Spring Scanner, and have added a JPOS target.

Now let's import the additional dependencies we want to use in our plugin:

<!-- Portfolio Public API -->
<dependency>
  <groupId>com.atlassian.rm</groupId>
  <artifactId>common-public-api</artifactId>
  <version>${jpos.version}</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>com.atlassian.rm</groupId>
  <artifactId>portfolio-public-api</artifactId>
  <version>${jpos.version}</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>com.atlassian.rm</groupId>
  <artifactId>team-management-public-api</artifactId>
  <version>${jpos.version}</version>
  <scope>provided</scope>
</dependency>

<!-- Spring annotations -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>${spring.version}</version>
  <scope>provided</scope>
</dependency>

To complete the spring scanner upgrade:

  • Change the <scope> of atlassian-spring-scanner-annotation to be provided, and not compile.
  • Remove the atlassian-spring-scanner-runtime dependency.
  • Remove the springframework and blueprint imports from the <Import-Package>*</Import-Package> block. In the sample below, you'll need to remove org.springframework.osgi.*;resolution:="optional", and org.eclipse.gemini.blueprint.*;resolution:="optional",
<Import-Package>
  org.springframework.osgi.*;resolution:="optional",
  org.eclipse.gemini.blueprint.*;resolution:="optional",
  *
</Import-Package>

Since our plugin needs Portfolio, and Portfolio needs Jira and Jira Software, let's add the necessary applications inside the maven-jira-plugin <configuration> block:

<applications>
  <application>
    <applicationKey>jira-software</applicationKey>
    <version>${jira.software.version}</version>
  </application>
  <application>
    <applicationKey>jira-core</applicationKey>
    <version>${jira.version}</version>
  </application>
</applications>

To make startup a bit more stable, we also added some magic sauce to the <configuration> block:

<jvmArgs>-Datlassian.plugins.enable.wait=300 -Dcom.atlassian.jira.startup.LauncherContextListener.SYNCHRONOUS=true -Xmx2048m -Xms512m</jvmArgs>

Next, you'll need to create or overwrite the file src/main/resources/META-INF/spring/plugin-context.xml with:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:atlassian-scanner="http://www.atlassian.com/schema/atlassian-scanner/2"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.atlassian.com/schema/atlassian-scanner/2
    http://www.atlassian.com/schema/atlassian-scanner/2/atlassian-scanner.xsd">
  <atlassian-scanner:scan-indexes/>
</beans>

The only difference should be the /2 after all the atlassian-scanner URLs.

Now, we can create pages and menus per usual.

For example, let's create a page template at src/main/resources/html/success.vm:

$webResourceManager.requireResource("com.atlassian.jpos.demo.api-demo:api-demo-resources")

<html>
  <head>
    <title>Portfolio for Jira Public API Tests</title>
  </head>
  <body>
    <h1>Portfolio for Jira Public API Says: $result</h1>
  </body>
</html>

You can then register your menu and web page in src/main/resources/atlassian-plugin.xml:

<web-section name="apiDemoSection" i18n-name-key="api-demo.name" key="demo-section" location="demo-item-link" weight="1000">
  <description key="api-demo.description">The demo-api plugin</description>
  <label key="demo-api.label"/>
  <link linkId="demo-api-link">/secure/DemoView.jspa</link>
</web-section>
<web-item name="apiDemoItem" i18n-name-key="api-demo.name" key="demo-item" section="system.top.navigation.bar" weight="1000">
  <description key="api-demo.description">The demo-api plugin</description>
  <label key="api-demo.label">demo</label>
  <link linkId="api-demo-link">/secure/DemoView.jspa</link>
</web-item>

<webwork1 key="api-demo-ww1" name="demo web actions" class="java.lang.Object">
  <actions>
    <action name="com.atlassian.jpos.demo.DemoView" alias="DemoView">
      <view name="success">/html/success.vm</view>
    </action>
  </actions>
</webwork1>

You can also configure some default translations in src/main/resources/api-demo.properties:

api-demo.label=JPOS API Demo
api-demo.description=A demo plugin that integrates with the JPOS Public API

Now, we can write a page view that calls the Portfolio API and renders the result to a page.

Create a new Java file at src/main/java/com/atlassian/jpos/demo/DemoView.java with:

package com.atlassian.jpos.demo;

import com.atlassian.jira.web.action.JiraWebActionSupport;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.rm.portfolio.publicapi.interfaces.plan.PlanAPI;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

public class DemoView extends JiraWebActionSupport {

  private final PlanAPI planApi;

  @Autowired
  public DemoView(@ComponentImport PlanAPI planApi) {
    this.planApi = planApi;
  }

  @Override
  public String execute() throws Exception {
    return "success";
  }

  @SuppressWarnings("unused")
  public String getResult() {
    try {
      long numPlans = planApi.count();
      return numPlans + " Plan" + (numPlans == 1 ? "" : "s");
    } catch (Exception e) {
      return "error: " + e.getMessage();
    }
  }
}

You can now build and run your plugin:

$ atlas-run

(or use atlas-debug during active development).

This will build and package your plugin, start up a version of Jira with Jira Software, and install your plugin.

Unfortunately, Portfolio is not available as a public application, so your plugin will fail to start (OSGi can't find the PlanAPI!). However, Jira with Jira Software should start up fine.

We can fix this by logging in (username: admin, password: admin), going to the Add-ons section under the Administration menu, and manually uploading the Portfolio plugin on the Manage add-ons tab.

Add-ons menu Manage Upload
Add-ons menu Manage add-ons Upload add-on

ℹ️ TIP: You should only have to manually add the Portfolio plugin after running atlas-mvn clean.

Now, you'll need to re-enable your plugin. You'll see two greyed-out copies of api-demo: one is the plugin, and the other is a testing module.

You can go ahead and enable them both -- though for this walkthrough, you'll only need to enable the copy with com.atlassian.jpos.demo.api-demo add-on key. This will start up Portfolio.

Enable plugin

Navigate to the Jira dashboard, or refresh the page, and the JPOS API Demo menu should now appear in the header.

Click JPOS API Demo to access your new Public API page, which should show the result of calling the Portfolio API.

Success!

Success! Now go forth and plan ahead!

You can find all the code on bitbucket.

Reach out to us in the User or Developer Communities if you have any questions, or even to share any new integrations with Portfolio that you may know of.

❤️

The Portfolio for Jira Server Team