Tutorial - Adding content to the JIRA View Issue page

Level of experience:

This is a beginner tutorial. This is a good tutorial to try if you have never developed an add-on before.

Time estimate:

It should take you approximately 1 hour to complete this tutorial.

On this page:

Overview of the tutorial

This tutorial shows you how to add content to the View Issue page in JIRA. It demonstrates how to create a plugin that adds a web panel to the page if the issue has a due date. Our web panel displays a colorful indication of when an issue is due (or overdue).

Your completed plugin will consist of the following components:

  • A plugin descriptor (XML file) that describes the plugin module to JIRA.
  • A Java class (DueDateIndicator) that calculates the due date for an issue and exposes it as an object context that we can use in JIRA UI templates.
  • Resources for displaying the plugin UI. This includes an internationalisation properties file (called tutorial-jira-add-content-to-view-issue-screen.properties) and a Velocity template (due-date-indicator.vm) for rendering the web panel plugin module.

When you have finished, all these components will be packaged in a single JAR file. 

About these Instructions

You can use any supported combination of OS and IDE to create this plugin. These instructions were written using Ubuntu Linux and Eclipse Galileo. If you are using another OS or IDE combination, you should use the equivalent operations for your specific environment.

This tutorial was last tested with JIRA 6.0.

Prerequisite knowledge

To complete this tutorial, you need to know the following: 

  • The basics of Java development: classes, interfaces, methods, how to use the compiler, and so on.
  • How to create an Atlassian plugin project using the Atlassian Plugin SDK.
  • The basics of using and administering JIRA.

Plugin Source

We encourage you to work through this tutorial. If you want to skip ahead or check your work when you have finished, you can find the plugin source code on Atlassian Bitbucket. Bitbucket serves a public Git repository containing the tutorial's code. To clone the repository, issue the following command:

git clone https://bitbucket.org/atlassian/tutorial-jira-add-content-to-view-issue-screen

Alternatively, you can download the source as a ZIP archive by choosing download here:

https://bitbucket.org/atlassian_tutorial/jira-add-content-to-view-issue-screen

Step 1. Create the plugin project

In this step, you'll use two atlas- commands to generate stub code for your plugin and set up the stub code as an Eclipse project. The atlas- commands are part of the Atlassian Plugin SDK, and automate much of the work of plugin development for you.

  1. If you have not already set up the Atlassian Plugin SDK, do that now: Set up the Atlassian Plugin SDK and Build a Project.
  2. Open a terminal and navigate to your Eclipse workspace directory.
  3. Enter the following command to create a plugin skeleton:

    atlas-create-jira-plugin
    
  4. Choose 1 for JIRA 5 when asked which version of JIRA you want to create the plugin for. 

  5. As prompted, enter the following information to identify your plugin:

    group-id

    com.example.plugins.tutorial

    artifact-id

    tutorial-jira-add-content-to-view-issue-screen

    version

    1.0-SNAPSHOT

    package

    com.example.plugins.tutorial

  6. Confirm your entries when prompted.
  7. Change to the tutorial-jira-add-content-to-view-issue-screen directory created by the previous step.
  8. Run the following command:

    atlas-mvn eclipse:eclipse
    
  9. Start Eclipse.
  10. Select File > Import.
    Eclipse starts the Import wizard.
  11. Filter for Existing Projects into Workspace (or expand the General folder tree).
  12. Choose Next and browse to the root directory of your plugin (where the pom.xml file is located).
    Your Atlassian plugin folder should appear under Projects.
  13. Select your plugin and choose Finish.
    Eclipse imports your project.

Step 2. Review and tweak the generated stub code

It is a good idea to familiarise yourself with the project configuration file, known as the POM (Project Object Model definition file). In this section, you will review and tweak the pom.xml file. Open your plugin project in Eclipse and follow along in the next sections.

Add plugin metadata to the POM

The POM (Project Object Model definition file) is located at the root of your project and declares the project dependencies and other information.

Add some metadata about your plugin and your company or organisation.

  1. Edit the pom.xml file in the root folder of your plugin.
  2. Add your company or organisation name and your website to the <organization> element (the following code blocks show how it looks in plain text):

    <organization>
        <name>Example Company</name>
        <url>http://www.example.com/</url>
    </organization>
    
  3. Update the description element:

    <description>This plugin shows the due date for an issue in a new panel on the View Issue page.</description>
    
  4. Optionally modify the name element to be something more readable:

    <name>Due Date Indicator on the 'View Issue' Page</name>
    

    This is the name for your plugin that will appear in the JIRA add-on administration page.

  5. Save the file.

Review the generated plugin descriptor

Your stub code contains a plugin descriptor file atlassian-plugin.xml. This is an XML file that identifies the plugin to the host application (JIRA) and defines the required plugin functionality. In your IDE (integrated development environment, such as Eclipse or IDEA) open the descriptor file located in your project under src/main/resources.

You should see something like this (comments removed):

<atlassian-plugin key="${project.groupId}.${project.artifactId}" 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>
    <resource type="i18n" name="i18n" location="tutorial-jira-add-content-to-view-issue-screen"/>
    <web-resource key="tutorial-jira-add-content-to-view-issue-screen-resources" name="tutorial-jira-add-content-to-view-issue-screen Web Resources">
        <dependency>com.atlassian.auiplugin:ajs</dependency>
        <resource type="download" name="tutorial-jira-add-content-to-view-issue-screen.css" location="/css/tutorial-jira-add-content-to-view-issue-screen.css"/>
        <resource type="download" name="tutorial-jira-add-content-to-view-issue-screen.js" location="/js/tutorial-jira-add-content-to-view-issue-screen.js"/>
        <resource type="download" name="images/" location="/images"/>
        <context>tutorial-jira-add-content-to-view-issue-screen</context>
    </web-resource>
    <component key="myPluginComponent" class="com.example.plugins.tutorial.MyPluginComponentImpl" public="true">
        <interface>com.example.plugins.tutorial.MyPluginComponent</interface>
    </component>
    <component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties" />
</atlassian-plugin>

Note that some of the information from the POM is transferred to the plugin descriptor using variable names such as ${project.artifactId}.

Step 3. Add your plugin modules

Now you will use the plugin module generator (another atlas- command) to generate the stub code for modules needed by the plugin.

For this tutorial, you will need a web panel plugin module. You'll add this via the atlas-create-jira-plugin-module command. The command can also generate an i18n properties file for your JIRA UI text, which we'll add as well.

  1. Open a command window and go to the plugin root folder (where the pom.xml is located).
  2. Run atlas-create-jira-plugin-module
  3. Choose the option labeled Web Panel.
  4. Supply the following information as prompted:

    Enter Plugin Module Name

    DueDateIndicator

    Enter Location

    atl.jira.view.issue.right.context

    See Right Side of the 'View Issue' Page Location for more information.

  5. At the Show Advanced Setup prompt, enter 'y' (for 'Yes').
  6. For the next five options, you can accept the default by pressing enter. For reference, the values are:

    Module Key

    due-date-indicator

    Module Description

    The DueDateIndicator Plugin

    i18n Name Key

    due-date-indicator.name

    i18n Description Key

    due-date-indicator.description

    Weight

    1000

  7. When prompted with Add Resource, choose 'y' and then enter the following information for the resource:

    Enter Resource Name

    view

    Enter Resource Type

    velocity

    Enter Location (path to resource file)

    due-date-indicator.vm

  8. At the Add Resource Parameter prompt, enter 'N' (for 'No').
  9. For Add Resource, choose 'N' (for 'No').
  10. For Add Velocity Context Provider, choose 'y' and enter the following information for the context provider:
    • Add Fully Qualified Context Provider Class: com.example.plugins.tutorial.DueDateIndicator
  11. For Add Conditions, choose 'N'.
  12. For Add Another Plugin Module, choose 'N'.

The SDK finishes generating your plugin module and returns you to the command prompt.

Step 4. Add a panel title

The SDK gave us most of what we need for the plugin configuration, but we'll need to add a title for our panel manually, as follows: 

  1. Open your plugin module descriptor file, atlassian-plugin.xml, for editing. It's in the src/main/resources directory.
  2. Add the following label element as a child of your web-panel element. The key parameter value represents a key in your i18n properties file.

    <web-panel name="DueDateIndicator" ...>
        ...
            <label key="due-date-indicator.title"/>
    </web-panel>
    
  3. Remove the component declaration generated by the SDK in the plugin descriptor, since we'll be removing the file later.

      <!-- publish our component --> 
     <component key="myPluginComponent" class="com.example.plugins.tutorial.MyPluginComponentImpl" public="true">
        <interface>com.example.plugins.tutorial.MyPluginComponent</interface>
      </component>
    

    Your plugin descriptor file should look something like this (comments removed for brevity):

    <?xml version="1.0" encoding="UTF-8"?>
    
    <atlassian-plugin key="${project.groupId}.${project.artifactId}" 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>
      <resource type="i18n" name="i18n" location="tutorial-jira-add-content-to-view-issue-screen"/>
      <web-resource key="tutorial-jira-add-content-to-view-issue-screen-resources" name="tutorial-jira-add-content-to-view-issue-screen Web Resources">
        <dependency>com.atlassian.auiplugin:ajs</dependency>
        <resource type="download" name="tutorial-jira-add-content-to-view-issue-screen.css" location="/css/tutorial-jira-add-content-to-view-issue-screen.css"/>
        <resource type="download" name="tutorial-jira-add-content-to-view-issue-screen.js" location="/js/tutorial-jira-add-content-to-view-issue-screen.js"/>
        <resource type="download" name="images/" location="/images"/>
        <context>tutorial-jira-add-content-to-view-issue-screen</context>
      </web-resource>
      <component-import key="applicationProperties" interface="com.atlassian.sal.api.ApplicationProperties"/>
      <web-panel name="DueDateIndicator" i18n-name-key="due-date-indicator.name" key="due-date-indicator" location="atl.jira.view.issue.right.context" weight="1000">
        <description key="due-date-indicator.description">The DueDateIndicator Plugin</description>
        <context-provider class="com.example.plugins.tutorial.DueDateIndicator"/>
        <resource name="view" type="velocity" location="due-date-indicator.vm"/>
        <label key="due-date-indicator.title"/>
      </web-panel>
    </atlassian-plugin>
    
  4. Save and close the file.
  5. Now open your i18n properties file tutorial-jira-add-content-to-view-issue-screen.properties for editing. It is also located in the src/main/resources/ directory.
  6. Specify the value of the due-date-indicator.title key you just defined in your plugin descriptor in your i18n properties file by adding this  due-date-indicator.title property:

    due-date-indicator.title=Due Date Indicator
    


    In the JIRA View Issue page, this value will appear as the title of your new web panel. Your i18n properties file should look like this:

    #put any key/value pairs here
    my.plugin.name=MyPlugin
    due-date-indicator.name=DueDateIndicator
    due-date-indicator.description=The DueDateIndicator Plugin
    due-date-indicator.title=Due Date Indicator
    
  7. Save and close the file.

Step 5. Write your Java class

So far you've created the framework for your web panel plugin module. Next write some Java code that makes the web panel do something interesting. Namely it:

  • Retrieves the due date associated with the current issue
  • Calculates the difference between the current data and the due date

We'll start with a little refactoring of the code that the SDK gave us:

  1. In the src/main/java/com/example/plugins/tutorial directory, rename MyPlugin.java to DueDateIndicator.java.
  2. Remove the MyPluginComponentImpl.java file also found in the same directory.
  3. Remove the test files that the SDK gave us.
    Testing is important of course, but outside the scope of this tutorial. You can remove the entire directory src/main/test.
  4. Open the DueDateIndicator.java file as just renamed, and replace its contents with:

    package com.example.plugins.tutorial;
    
    import com.atlassian.crowd.embedded.api.User;
    import com.atlassian.jira.issue.Issue;
    import com.atlassian.jira.plugin.webfragment.contextproviders.AbstractJiraContextProvider;
    import com.atlassian.jira.plugin.webfragment.model.JiraHelper;
    
    import java.sql.Timestamp;
    import java.util.HashMap;
    import java.util.Map;
    
    public class DueDateIndicator extends AbstractJiraContextProvider
    {
        @Override
        public Map getContextMap(User user, JiraHelper jiraHelper) {
            return null;
        }
    }
    


    Notice that it now extends the abstract class AbstractJiraContextProvider and implements the class' getContextMap method.

    But that's just the beginning. The class will need to retrieve the current time as the number of milliseconds which have passed since midnight, 1st of January 1, 1970 UTC), by calling java.lang.System.currentTimeMillis(). We will use this value to calculate the difference between the current time and the due date time of the current issue (using the getTime() method against the due date's Timestamp), which is also retrieved as the number of milliseconds that have passed since midnight, 1st of January 1, 1970 UTC.

  5. Add a constant to your class named MILLIS_IN_DAY, which gets the number of milliseconds in a day:

    private static final int MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
    

    We'll use that in subsequent calculations.

  6. The getContextMap method we implemented needs to return a Map object with a key and value that represents the number of days difference between the due date and the current date, so that it can be used by other parts of the module. To do this, initialise a new HashMap Map object within the method (called contextMap) to store this key and value.

    Map contextMap = new HashMap();
    
  7. Also within your getContextMap method, initialise a new JIRA Issue object currentIssue to store the current JIRA issue as an object through which you can access its due date as a Java Timestamp, using the getDueDate() method:

    Issue currentIssue = (Issue) jiraHelper.getContextParams().get("issue");
    Timestamp dueDate = currentIssue.getDueDate();
    
  8. Check to see if the current issue's due date has been set and if so begin calculating the number of days difference between the current date and due date. Since the time of the dueDate Java Timestamp will be accessed using the getTime() method which, like the currentTimeMillis() method described above, returns the time as the number of milliseconds which have passed since midnight, 1st of January 1, 1970 UTC, we need to convert these to days by dividing each of these values by the MILLIS_IN_DAYconstant and casting the values to integers:

    if (dueDate != null)
    {
        int currentTimeInDays = (int) (System.currentTimeMillis() / MILLIS_IN_DAY);
        int dueDateTimeInDays = (int) (dueDate.getTime() / MILLIS_IN_DAY);
    }
    
  9. Now that we have the number of days for the due date time (dueDateTimeInDays) and current date time (currentTimeInDays) both from a common reference point, we need to calculate the difference between these values, adding one day for the final calculation. Associate this value (daysAwayFromDueDateCalc) with the keyword daysAwayFromDueDate via the Map object (contextMap). Add the following lines of code within the if statement above:

    int daysAwayFromDueDateCalc = dueDateTimeInDays - currentTimeInDays + 1;
    contextMap.put("daysAwayFromDueDate", daysAwayFromDueDateCalc);
    
  10. Finally, your DueDateIndicator class should return the contextMap Map object back to your web panel plugin module to make it available to other resources in your plugin (i.e., our Velocity Template resource, which you define in the next step).

    return contextMap;
    
  11. Save the file.

The complete code for DueDateIndicator should look like this:

package com.example.plugins.tutorial;

import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.plugin.webfragment.contextproviders.AbstractJiraContextProvider;
import com.atlassian.jira.plugin.webfragment.model.JiraHelper;

import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;

public class DueDateIndicator extends AbstractJiraContextProvider
{
    private static final int MILLIS_IN_DAY = 24 * 60 * 60 * 1000;

    @Override
    public Map getContextMap(User user, JiraHelper jiraHelper) {
        Map contextMap = new HashMap();
        Issue currentIssue = (Issue) jiraHelper.getContextParams().get("issue");
        Timestamp dueDate = currentIssue.getDueDate();
        if (dueDate != null)
        {
            int currentTimeInDays = (int) (System.currentTimeMillis() / MILLIS_IN_DAY);
            int dueDateTimeInDays = (int) (dueDate.getTime() / MILLIS_IN_DAY);
            int daysAwayFromDueDateCalc = dueDateTimeInDays - currentTimeInDays + 1;
            contextMap.put("daysAwayFromDueDate", daysAwayFromDueDateCalc);
        }
        return contextMap;
    }
}

Step 6. Write your Velocity template

So far, you've written a Java class that retrieves the difference in days between the current issue's due date and the current date. Now you will write a Velocity template to present this information in HTML format.

Your web panel plugin module already contains the following context provider:

<context-provider class="com.example.plugins.tutorial.DueDateIndicator"/>

This is your DueDateIndicator Java class. As already coded, this class returns a Map object with the property daysAwayFromDueDate whose value is the number of days difference between the current issue's due date and the current date. The daysAwayFromDueDate property is available to other resources in the plugin module, such as the Velocity template resource you already defined in the plugin descriptor using the SDK, due-date-indicator.vm. However, while we declared it in the descriptor, we still need to add the file, as follows:

  1. Change to the src/main/resources directory and create a new file named due-date-indicator.vm.
  2. Add the following if/then/else statement to the due-date-indicator.vm file:

    #set ($overdueDays = $daysAwayFromDueDate * -1)
    #if ($daysAwayFromDueDate > 1)
        <span style="font-weight: bold; color: green;">This issue is due in $daysAwayFromDueDate days.</span>
    #elseif ($daysAwayFromDueDate == 1)
        <span style="font-weight: bold; color: blue;">This is issue is due tomorrow.</span>
    #elseif ($daysAwayFromDueDate == 0)
        <span style="font-weight: bold; color: purple;">This issue is due today.</span>
    #elseif ($daysAwayFromDueDate == -1)
        <span style="font-weight: bold; color: #ff4500;">This issue was due yesterday!</span>
    #elseif ($daysAwayFromDueDate < -1)
        <span style="font-weight: bold; color: red;">This issue is overdue by $overdueDays days!</span>
    #end


    This provides a colorful visual indicator of when the current issue is due. Notice that we've defined a new variable, $overdueDays. Since the daysAwayFromDueDate property returned by our Java class contains a negative value whenever an issue is overdue (which is what our if/then/else statement series relies upon), we've created this new variable to present the value of daysAwayFromDueDate as a positive number.
    Also note that is a deep-red orange color.

  3. Save the file.

Step 7. Build, install and run the plugin

Now you're ready to try out your plugin. Start JIRA as follows:

  1. In a new console, navigate to the project root directory
  2. Enter the following Atlassian Plugin SDK command:

    atlas-run
  3. When JIRA finishes starting up, use a browser to open the JIRA address indicated in the console printout, such as http://localhost:2990/jira.

  4. Log in using the default username and password for a dev mode instance, admin/admin.

  5. If you're using a recent version of JIRA, you'll see a wizard for creating a new JIRA project upon successful login. Follow the steps to create one. 

  6. Now create a new issue. In your issue, add a due date and save.

  7. Navigate to your new issue, and voila! You should see your Due Date Indicator web panel:

Congratulations, that's it

Have a chocolate!

Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport