en
de
November 2018

A Plugin for Atlassian Jira – Connect the UI to Jira Data

12 December 2000
| |
Reading time: 3 minutes

Previous Page | Next Page | Pages: 0 1 2 3 4 5 6 7

Access to Jira data

At this point we have to provide additional source files: the Velocity template and the context provider class mentioned in the module configuration above. Suggested initial, extremely simple implementations are shown below.

The Velocity template is a recipe for generating a chunk of HTML that the web application server inserts in a web page. As you can see in the example below, it obtains variable text from context variables. In this case, aside from the built-in variables such as $i18n, which is an object capable of returning localised text for any given key (and which should of course be used for all the field labels instead of hard-coding them), we need a variable named $project. The context provider class (in this example, MetricsInfoImpl) is named in the web-panel module defined earlier. It has to provide a public method getContextMap, which returns the context information in the form of key-value pairs. Values can be complex objects – Velocity is clever enough to dereference the required attribute or property by repeatedly calling get… until it has descended to the desired level (e.g. $project.lead.name).

metrics-tab-panel.vm (in src/main/resources/templates/tabpanels)

<div class="mod-header">
    <h3>$i18n.getText('metrics--page--item.label')</h3>
    <table>
        <tr>
            <td>Name</td><td>$project.name</td>
        </tr>
        <tr>
            <td>Key</td><td>$project.key</td>
        </tr>
        <tr>
            <td>Description</td><td>$project.description</td>
        </tr>
        <tr>
            <td>Project Lead Key</td><td>$project.lead.key</td>
        </tr>
        <tr>
            <td>Project Lead Name</td><td>$project.lead.name</td>
        </tr>
        <tr>
            <td>Project Lead Display Name</td><td>$project.lead.displayName</td>
        </tr>
        <tr>
            <td>Project Lead Email Address</td><td>$project.lead.emailAddress</td>
        </tr>
    </table>
</div>

MetricsInfo.java

package com.zuhlke.training.jira.api;

import com.atlassian.jira.plugin.webfragment.model.JiraHelper;
import com.atlassian.jira.user.ApplicationUser;

import java.util.Map;

public interface MetricsInfo {
    String PROJECT = "project";

    Map getContextMap(ApplicationUser applicationUser, JiraHelper jiraHelper);
}

MetricsInfoImpl.java

There are numerous ways to get at information in Jira. Where the desired information is related to a project, the UserProjectHistoryManager turns out to be the most straightforward. There is another class named ProjectManager, which is easier to use in some circumstances – particularly where you already know the project key or you want a list of all projects in the system. However, in this case, we just want the currently selected project.

package com.zuhlke.training.jira.impl;

import com.atlassian.jira.plugin.webfragment.contextproviders.AbstractJiraContextProvider;
import com.atlassian.jira.plugin.webfragment.model.JiraHelper;
import com.atlassian.jira.project.Project;
import com.atlassian.jira.user.ApplicationUser;
import com.atlassian.jira.user.UserProjectHistoryManager;
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;

import java.util.HashMap;
import java.util.Map;

import static com.atlassian.jira.security.Permissions.PROJECT_ADMIN;

@Scanned
public class MetricsInfoImpl extends AbstractJiraContextProvider implements com.zuhlke.training.jira.api.MetricsInfo {

    @ComponentImport
    private final UserProjectHistoryManager userProjectHistoryManager;

    public MetricsInfoImpl(UserProjectHistoryManager userProjectHistoryManager){
        this.userProjectHistoryManager = userProjectHistoryManager;
    }

    @Override
    public Map getContextMap(ApplicationUser applicationUser, JiraHelper jiraHelper) {
        Map<String, Object> contextMap = new HashMap<>();

        Project currentProject = userProjectHistoryManager.getCurrentProject(PROJECT_ADMIN, applicationUser);
        if(null != currentProject) {
            contextMap.put(PROJECT, currentProject);
        }

        return contextMap;
    }
}

The annotation @Scanned tells the Atlassian Spring scanner to scan the class at build time. From https://www.j-tricks.com/tutorials/atlassian-spring-scanner-and-nosuchbeandefinitionexception:

  • Anything in the constructor needs the @ComponentImport annotation and the class needs a @Scanned annotation
  • You can use the components using ComponentAccessor, without the annotations:
    YourComponent yourComponent = ComponentAccessor.getComponent(YourComponent.class);
  • But make sure that your component class itself has the required annotations to publish the class as a component: @Component and @ExportAsService, as required.

It is therefore probably a good idea to add the @Scanned annotation to the MyPluginComponentImpl class too, for the sake of consistency.

Final configuration

Eventual content of atlassian-plugin.xml:

<?xml version="1.0" encoding="UTF-8"?>

<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2">
  <plugin-info>
    <description>${project.description}</description>
    <version>${project.version}</version>
    <vendor name="${project.organization.name}" url="${project.organization.url}"/>
    <param name="plugin-icon">images/pluginIcon.png</param>
    <param name="plugin-logo">images/pluginLogo.png</param>
  </plugin-info>
  <!-- add our i18n resource -->
  <resource type="i18n" name="i18n" location="myPlugin"/>
  <!-- add our web resources -->
  <web-resource key="myPlugin-resources" name="myPlugin Web Resources">
    <dependency>com.atlassian.auiplugin:ajs</dependency>
    <resource type="download" name="myPlugin.css" location="/css/myPlugin.css"/>
    <resource type="download" name="myPlugin.js" location="/js/myPlugin.js"/>
    <resource type="download" name="images/" location="/images"/>
    <context>myPlugin</context>
  </web-resource>
  <web-item name="metrics-page-item" i18n-name-key="metrics--page--item.name" key="metrics--page--item" section="jira.project.sidebar.plugins.navigation" weight="1000">
    <description key="metrics--page--item.description">Menu item for Metrics Page</description>
    <icon width="32" height="32">
      <link linkId="metrics--page--item-icon">${baseurl}/download/resources/${atlassian.plugin.key}:myPlugin-resources/images/arrows.png</link>
    </icon>
    <label key="metrics--page--item.label"/>
    <link linkId="metrics--page--item-link">/projects/${pathEncodedProjectKey}?selectedItem=com.atlassian.jira.jira-projects-plugin:metrics-page</link>
  </web-item>

  <web-panel name="metrics-page" i18n-name-key="metrics--page.name" key="metrics--page" location="com.atlassian.jira.jira-projects-plugin:metrics-page" weight="1000">
    <description key="metrics--page.description">Agile Metrics page</description>
    <context-provider class="com.zuhlke.training.jira.impl.MetricsInfoImpl"/>
    <resource name="view" type="velocity" location="templates/tabpanels/metrics-tab-panel.vm"/>
  </web-panel>
</atlassian-plugin>

The full specification of the maven-jira-plugin in pom.xml:

<plugin>
    <groupId>com.atlassian.maven.plugins</groupId>
    <artifactId>maven-jira-plugin</artifactId>
    <version>${amps.version}</version>
    <extensions>true</extensions>
    <configuration>
        <productVersion>${jira.version}</productVersion>
        <productDataVersion>${jira.version}</productDataVersion>
        <jvmArgs>-Xms1g -Xmx1g -XX:MaxPermSize=1g -XX:-UseGCOverheadLimit -server</jvmArgs>
        <!--Uncomment to install TestKit backdoor in JIRA.-->
        <pluginArtifacts>
            <pluginArtifact>
                <groupId>com.atlassian.jira.tests</groupId>
                <artifactId>jira-testkit-plugin</artifactId>
                <version>${testkit.version}</version>
            </pluginArtifact>
        </pluginArtifacts>
        <enableQuickReload>true</enableQuickReload>
        <enableFastdev>false</enableFastdev>
        <!-- See here for an explanation of default instructions: -->
        <!-- https://developer.atlassian.com/docs/advanced-topics/configuration-of-instructions-in-atlassian-plugins -->
        <instructions>
            <Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>
            <!-- Add package to export here -->
            <Export-Package>
                <!-- package name from your interface e.g. MyPluginComponent.java here -->
                path.to.api
            </Export-Package>
            <!-- Add package import here -->
            <Import-Package>
                org.springframework.osgi.*;resolution:="optional",
                org.eclipse.gemini.blueprint.*;resolution:="optional",
                *
            </Import-Package>
            <!-- Ensure plugin is spring powered -->
            <Spring-Context>*</Spring-Context>
        </instructions>
    </configuration>
</plugin>

Start the Server

At this point, you should be able to execute atlas-clean followed by atlas-run or atlas-debug. Note that starting the server takes a minute or two, and you’ll probably notice a series of stack traces complaining about missing jar files. Provided that you don’t find the string “failed to load” in the output, there is nothing to worry about. Point your browser at http://localhost:2990/jira and log in with admin/admin.

If things are going wrong, check the log files in these locations:

  • target/container/tomcat8x/cargo-jira-home/logs
  • target/jira/home/log

Don’t forget to commit your work!

Previous Page | Next Page | Pages: 0 1 2 3 4 5 6 7

Comments (0)

×

Sign up for our Updates

Sign up now for our updates.

This field is required
This field is required
This field is required

I'm interested in:

Select at least one category
You were signed up successfully.

Receive regular updates from our blog

Subscribe

Or would you like to discuss a potential project with us? Contact us »