Skip to end of metadata
Go to start of metadata

Applicable:

This tutorial applies to JIRA 5.0 and later.

Level of experience:

This is an advanced tutorial. You should have completed at least one intermediate tutorial before working through this tutorial. See the list of tutorials in DAC.

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 create custom JIRA reports. In this tutorial, you'll add two reports:

  • Single Level Group By Extended report
  • Issue Creation report 

The Creation Report displays a histogram of issues created over a specified time and broken into certain intervals of time.

The Single Level Group By Extended report builds on an existing report in JIRA. The existing report looks like this:

When you're done, you'll have a new report that looks like this:

Notice the assignee and last modified date in the output.

Your completed plugin will consist of the following components:

  • Java classes encapsulating the plugin logic.
  • Resources for display of the plugin user interface (UI).
  • A plugin descriptor to present the plugin UI in JIRA.

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

About these Instructions

Icon

You can use any supported combination of OS and IDE to create this plugin. These instructions were written using IntelliJ IDEA on Ubuntu Linux. 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.4.

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.
  • This tutorial also involves creating Apache Velocity templates. To extend the tutorial code, you should have a good handle on how Velocity templates work.

Plugin Source

We encourage you to work through this tutorial. If you want to skip ahead or check your work when you are done, 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://atlassian_tutorial@bitbucket.org/atlassian_tutorial/jira-report-plugin.git

Alternatively, you can download the source using the Downloads page here: https://bitbucket.org/atlassian_tutorial/jira-report-plugin

Step 1. Create the Plugin Project

In this step, you'll use the Atlassian Plugin SDK to generate the scaffolding for your plugin project. The Atlassian Plugin SDK automates much of the work of plugin development for you. It includes commands for creating a plugin and adding modules to the plugin.

  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. In the directory where you want to put the plugin project, enter the following SDK command:

    atlas-create-jira-plugin
    
  3. Choose 1 for JIRA 5 when asked which version of JIRA you want to create the plugin for. 
  4. As prompted, enter the following information to identify your plugin:

    group-id

    com.atlassian.plugins.tutorial.jira

    artifact-id

    jira-report-plugin

    version

    1.0-SNAPSHOT

    package

    com.atlassian.plugins.tutorial.jira

  5. Confirm your entries when prompted.

The SDK finishes up and generates a directory for you with the initial project files, including a POM (Project Object Model definition file), stub source code, and resources.

Step 2. Review and tweak the generated stub code

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

Add plugin metadata to the POM

The POM is located at the root of your project and declares the project dependencies and other information. Add metadata about your plugin and your company or organisation as follows:

  1. Edit the pom.xml file in the root folder of your plugin.
  2. Add your company or organization name and website to the organization element:

  3. Update the project description element as follows:

  4. Remove the commenting from around the dependency element for the jira-core artifact, including it in your project.
    This tutorial extends an existing JIRA report that relies on APIs in the JIRA core package. So while you normally won't need to do this, you do for this tutorial.
  5. Save and close 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. Open the descriptor file. You can find it under the src/main/resources directory of your project home.

You should see something like this (comments removed):  

Step 3. Add the plugin modules

Now you will use the plugin module generator (another atlas command) to generate the stub code for modules for the plugin. For your modules, add two report modules as follows:

  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. Enter the number for the Report module.
  4. As prompted, enter the following:

    Enter New Classname

    SingleLevelGroupByReportExtended

    Package Name

    com.atlassian.plugins.tutorial.jira.reports

    Choose N for Show Advanced Setup.

  5. Choose Y for Add Another Plugin Module.
  6. Again enter the number for the Report module.
  7. Enter the following:

    Enter New Classname

    CreationReport

    Package Name

    com.atlassian.plugins.tutorial.jira.reports

  8. Choose N for Show Advanced Setup.
  9. Choose N for Add Another Plugin Module.
  10. Confirm your choices.

The SDK generates the code files for the modules, and adds them to the plugin descriptor. It also adds other resources, such as Velocity files and i18n resource files.

Step 4. Add module properties

Properties are the configurable fields that the plugin exposes in the JIRA UI. We'll add a few properties to the module definition:

  1. Open the file src/main/resources/atlassian-plugin.xml.

  2. Uncomment the properties element under the Single Level Group By Report Extended module and replace the default property elements it contains with these two new ones: 

    We're adding two new properties, a filter picker and a selector for the statistics type by which to group the result.

  3. Similarly, uncomment and replace the properties element under the Single Level Group By Report Extended module with the following:

    Here we've added:

    • projectid allows users to choose the project or filter used to generate the report. The projects that are available in a given JIRA instance are retrieved through JIRA's filterprojectpicker facility.
    • startDate sets the start of the time period included in the report.
    • endDate sets the end of the time period for the report.
    • interval specifies the time interval used to divide the overall time period. This is the histogram interval, in other words.

So far, we've been working on two modules at once, each of which corresponds to separate reports in JIRA. From here, let's take them one at a time, starting with the Single Level Group By Extended report.

Step 5. Write the Single Level Group By Extended report code

When you used the SDK to create the modules, it gave you the stub code files for the reports. The stub code is very simple: just a constructor and a few imports. We'll build on it now.

For our first report, we're extending a report delivered with JIRA, the SingleLevelGroupByReport class. If you have access to the JIRA source code, you can find the source code for the original at this location:

jira-components/jira-core/src/main/java/com/atlassian/jira/plugin/report/impl/SingleLevelGroupByReport.java

As a reminder, our goal is to include the time of last update for each issue in the report output. It should be rendered in the appropriate date/time format configured in JIRA. The view template gets values to display in JIRA through the parameter map passed by the module code. Thus, we'll modify the parameter map generated by the original JIRA report to add the OutLookDateManager's OutlookDate object to the Velocity template.

  1. Open the source file for the report that the SDK gave us, SingleLevelGroupByReportExtended.java. The file is under the project home at:
    src/main/java/com/atlassian/plugins/tutorial/jira/reports

  2. Replace its contents with the following. This is the source code of the original report, but tweaked to account for a different classname and package.

    As mentioned, this is simply the original report. Next, add the code that presents the time of last update to the report.

  3. Under the existing field declarations for the class, add a new field:

  4. Also add the new field as a parameter passed to the class constructor.

  5. In the generateReportHtml() method, add the following line. It should appear within the try block in which the code assigns values to the parameter map.

    This code appends an additional parameter to the parameter map created by the method.

  6. Save your changes.

The class as modified should look something like this:

Step 6. Create the view template for the Single Level Group By report

Next, edit the report view template to display the assignee and the issue's last update time. This version differs from the original JIRA report in two places where we loop over the issue result set to display the time.

Let's update the resource files that the SDK gave us.

  1. Open the template for the Single Level Group By Extended report: src/main/resources/templates/reports/single-level-group-by-report-extended/view.vm
  2. Replace the placeholder content with the following:

    This template code is identical to the template for the original report, but with an added table cell in the output that contains our date.

  3. Open the properties file, src/main/resources/SingleLevelGroupByReportExtended.properties.

  4. Replace its contents with the following:

    These are the text strings that will appear in the UI generated by the report.

  5. Save and close the file.

That gives us enough to try it out in JIRA. You'll do that next.

Step 7. Start up JIRA and try the report

See what we've got so far by starting up JIRA:

  1. At the command line, change to the project root directory (where the POM is located), and enter the following command:

    Give the SDK a few minutes to download the JIRA files and start it up. If you run into build errors, make sure you have enabled the dependency for the jira-core artifact in the project POM.

  2. Once JIRA finishes starting up, open the JIRA interface in a browser with the default credentials, admin/admin.
  3. Before trying out your report, create a few JIRA artifacts first:
    • Create a project. The first time your start JIRA, a wizard prompts you to create one.
    • Create a few test issues in the project. To give your report a property to group by, choose different assignees or issue types for your issues. 
    • Also create at least one filter. You can do this from the search page. For more help, see the documentation on JIRA filters.  
  4. When ready to run the report, go to the Overview page for your project.
  5. In the Summary view, scroll down and notice your two new reports at the bottom of the list.
  6. Click Single Level Group By Report Extended
  7. Select a Filter and the statistic type you want to group by, and click Next. Your report should look something like this:

While you're at it, take a look at how the other custom report, Creation Report, looks in JIRA. We have our user input fields, thanks to the properties you added to the plugin descriptor, but there are only placeholders for labels and if you click Next, you get a blank page. You'll work on that next.

Meanwhile, you can leave JIRA running and load your plugin changes with FastDev.

Step 8. Code the Creation Report

To make the Creation Report do something a little more useful:

  1. Open the Creation Report source code file at:
    src/main/java/com/atlassian/plugins/tutorial/jira/reports/CreationReport.java

  2. Replace its contents with the following.


    The code retrieves the parameters specified by the user. It then gets the relevant issue counts from the configured time range, divided into the time intervals specified by the user. The issue counts are normalized, giving the user a balanced histogram. Finally, the relevant details are passed to the Velocity template.

    Icon

    For more details on what the code is doing, look at code comments included in the source code in the JIRA Report BitBucket repository.

  3. Save and close the file.

Step 9. Develop the Creation Report UI

If you tried the report in JIRA now, you would still get a blank page. To see a report, you need to code a presentation template, along with text strings that will appear in the UI. To do so: 

  1. Open the Creation Report velocity template file for editing: src/main/resources/templates/reports/creation-report/view.vm.
  2. Replace its content with the following:

  3. Add the following text strings to the properties file

  4. Save and close the file.

Step 10. View the finished product

Now try the Creation Report again and see what we've got:

  1. Start up JIRA again using atlas-run or, if you left JIRA running, reload your plugin changes using the compile and reload icon in FastDev:
  2. At the bottom of the project summary page, click the Creation Report link.
  3. Supply dates that make a range that encompasses the date on which you created the issues, and long enough to give you a few intervals to look at (keeping in mind the default interval of 3 days).
  4. Click Next
    This time, we have a report!

Next Steps

Our report output UI is pretty basic, to put it politely. As a next step, try improving the report output. You can use the reports supplied with JIRA as a model.

For more information on topics covered in this tutorial, see:

 

Congratulations, that's it

Icon

Have a chocolate!

 

 

21 Comments

  1. I am trying to modify example 1 (Extending an Existing System Report), in order to display the components within the report.

    I am new to Velocity templates and I am not sure how to return the names of the components?  

    I can get the size of the returned collection for the components by using $issue.getComponents().size() however I would really like to display the components names.

    Any help with this would be really apprieciated as I am getting no joy from the development forums.

    Thanks,

    Kevin

    1. Hi Kevin,

      The getComponents() method gives you a regular Java collection. You're calling size() on it, which works the way you expect it to; you can also iterate over it in Velocity using a #foreach loop:

      That gives you a reference to each component, and you can do whatever you need with each of them. Have a look at the velocity user guide if you need more info.

      Cheers,
      dr

  2. Guys, Is it possible to have projects list for selection and dependable list of versions to select?
    As I see it all standard reports works with the current project and do not allow user to select it.
    That's why they no need to re-initialize the list of version on input screen.

    But I want to have let's say
    1. combo box with 2 projects - single selection is allowed
    2. depending on the selection in project's combo box I want to load project's versions automatically to the second combo box for choice

    Can I make it? even with re-load of the page?

    1. Hi Alexander,

      Looking at the JIRA API, it looks to me like what you want should be possible. In the constructor for your Report class, you get a ProjectManager object. To get a list of all projects, you'd call ProjectManager.getProjectObjects() or something like that. You'd populate your first combo box with that data (using Velocity code similar to above).

      After that, there are a few different ways you might choose to go. Possibly the cleanest way would be to create a REST service using the REST plugin module type, which would give you a nice, simple way to get a list of versions for a given product. Alternatively, you could check out the SOAP API for JIRA, and see if that's helpful. In either case, the goal is of course to populate the second combo box, using the contents of Project.getVersions() without a full page reload.

      If you're comfortable writing REST services, you may also choose to populate the first combo box (for projects) using a REST call, rather than the Velocity stuff.

      Best,
      dr

      1. Hi Dan,

        Thanks for your suggestions.
        As I understand you, I need to remove standard parameters screen (build by JIRA by default in case of usage properties in reports' descriptor like:
        <properties>
        <property>
        <key>projectId</key>
        <name>report.timereport.project.name</name>
        <description>report.timereport.project.description</description>
        <type>select</type>
        <values class="com.blah.blah.blah.util.ExtendedProjectValuesGenerator"/>
        </property>
        <property>
        <key>versionId</key>
        <name>report.timereport.version.name</name>
        <description>report.timereport.version.description</description>
        <type>select</type>
        <values class="com.blah.blah.blah.util.ExtendedVersionValuesGenerator"/>
        ) and develop my own parameters screen.
        I think everything is possible (REST plugin) - just need open information sources.
        Could you describe a little bit more/in details how can I design the screens, where I'll be able to collect input parameters and the second screen for displaying the report content?

        1. Hey Alexander, did you create your report? I need to do the same you were doing here. Can you post your solution? thanks.

  3. I'm trying to follow example 1, but I'm getting the following error.  What am I missing?

    There were errors loading this plugin: file:/C:/Atlassian/MyPlugins/jira-report-plugin/target/container/tomcat6x/cargo-jira-home/webapps/jira/WEB-INF/lib/jira-report-plugin-1.0.jar\!/atlassian-plugin.xml: com.atlassian.plugin.PluginParseException: Unable to load plugin resource: null - The key is required: <properties> <property> <key>filterid</key> <name>report.singlelevelgroupby.filterId</name> <description>report.singlelevelgroupby.filterId.description</description> <type>select</type> <values/> </property> <property> <key>mapper</key> <name>report.singlelevelgroupby.mapper</name> <description>report.singlelevelgroupby.mapper.description</description> <type>select</type> <values/> </property> </properties>

    1. hi Peter,

      it looks like you forgot to set the key attribute to one of the XML element of your atlassian-plugin.xml.

      1. Thanks for responding.  I copied the content of atlassian-plugin.xml from the box in the tutorial.  I've pasted the content below.  I'm hoping you'll be able to see what I'm missing.

        <report key="singlelevelgroupbyextended" name="Example: Group By Report Extended">
            <description key="report.singlelevelgroupby.description">i18n description</description>
            <resource type="velocity" name="view" location="templates/groupreport/single-groupby-report-extended.vm" />
            <resource type="i18n" name="i18n" location="com.atlassian.plugins.tutorial.jira.report.singlelevelgroup_report" />
            <label key="report.singlelevelgroupby.label.extended" />
            <properties>
                <property>
                    <key>filterid</key>
                    <name>report.singlelevelgroupby.filterId</name>
                    <description>report.singlelevelgroupby.filterId.description</description>
                    <type>select</type>
                    <values />
                </property>
                <property>
                    <key>mapper</key>
                    <name>report.singlelevelgroupby.mapper</name>
                    <description>report.singlelevelgroupby.mapper.description</description>
                    <type>select</type>
                    <values />
                </property>
            </properties>
        </report>
        
        1. Hi Peter,

          I went through the tutorial quickly and didn't have any issues.

          It appears to be that your <values /> are empty when they are not in the tutorial. This could be the cause of your problems.

          May I suggest you checkout the code from SVN, at http://svn.atlassian.com/svn/public/contrib/tutorials/jira-report-plugin/trunk/
          and work from there...

  4. I am trying to create my own plugin based on this tutorial and I am wondering how we can change what is displayed on the issue line item? For instance I would like to be able to display the time spent on the issue.

    Cheers,
    Harold

    1. Hi Harold,

      The #issueLineItem($issue) is a velocity macro. You can get the contents of this macro from the full JIRA src in src/etc/java/templates/plugins/jira/macros.vm (search for issueLineItem).

      In case you don't have access to the source, the macro contains this:

      <td width="5%">#displayType($issue)</td>
      <td width="5%" nowrap><a href="$req.contextPath/browse/$issue.getKey()">$issue.getKey()</a></td>
      <td width="5%" nowrap>
      
          #if ($fieldVisibility && $fieldVisibility.isFieldHidden("resolution", $issue) == false)
              #if ($issue.getResolutionObject())
                  $textutils.htmlEncode($!issue.getResolutionObject().getNameTranslation().toUpperCase(), false)
              #else
                  <em>$i18n.getText("common.status.unresolved")</em>
              #end
          #end
      
      </td>
      <td width="80%">
      
              #if ($issue.isSubTask())
                  #set ($parentIssue = $issue.getParentObject())
                  #if ($permissionCheck && $permissionCheck.isIssueVisible($parentIssue) == true)
                      <a href="$req.contextPath/browse/$parentIssue.getKey()" style="text-decoration: none;" title="$textutils.htmlEncode($parentIssue.getSummary())">$parentIssue.getKey()</a><br/>
                  #else
                      <span class="smallgrey">$parentIssue.getKey()</span><br/>
                  #end
                  <img src="$req.contextPath/images/icons/link_out_bot.gif" width="16" height="16" border="0" align="absmiddle" alt="" />
              #end
          <a href="$req.contextPath/browse/$issue.getKey()">$textutils.htmlEncode($issue.getSummary())</a>
      
      </td>
      <td nowrap width="1%">
      #if ($fieldVisibility && $fieldVisibility.isFieldHidden("priority", $issue) == false)
          #if ($issue.getPriorityObject())
              #displayConstantIcon($issue.getPriorityObject())
          #end
      #end
      </td>
      <td nowrap width="1%">
      #displayConstantIcon($issue.getStatusObject())
      </td>
      

      So in your example, you can just replace #issueLineItem($issue) with the code above, then add time spent to this.

      Cheers,
      Andreas

  5. Is it possible to create custom report based on custom database view  in jira?

  6. Are there a tutorial for creation a jira report for JIRA 3.13.1 ??

  7. Having a few issues, don't know how important the are.

    First:

    Eclipse says that User is deprecated. Will this be changed?

    Second:

    I tried to find documentation for this one. But it seems to be one level to high. Everything starts at com.atlassian.jira..

    Any help will be appreciated.

  8. Eirik, I believe 'com.opensymphony.user.User' class was replaced by 'com.atlassian.crowd.embedded.api.User', which is the "logged in user" which you can obtain from the method bellow:

    http://docs.atlassian.com/jira/latest/com/atlassian/jira/web/action/JiraWebActionSupport.html#getLoggedInUser()

  9. Anonymous

    As I was trying to test the SigleLevelGroupByReportExtended I faced the following java error :

    C:\JiraReport\jira-report-plugin\src\main\java\com\atlassian\plugins\tutorial\jira\reports\SingleLevelGroupByReportExtended.java:[94,37] cannot find symbol

    symbol  : constructor OneDimensionalDocIssueHitCollector(java.lang.String,com.atlassian.jira.issue.statistics.StatsGroup,org.apache.lucene.index.IndexReader,com.atlassian.jira.issue.IssueFactory,com.atlassian.jira.web.FieldVisibilityManager,com.atlassian.jira.issue.search.ReaderCache)

    location: class com.atlassian.jira.issue.statistics.util.OneDimensionalDocIssueHitCollector

     

    Any clues how to solve this ?

  10. Anonymous

    Hi,

    I am also getting the same error as above when I run atlas-run. Any help is appreciated.

    [INFO] Compiling 4 source files to /root/atlastutorial/jira-report-plugin/target/classes

    [INFO] ------------------------------------------------------------------------

    [ERROR] BUILD FAILURE

    [INFO] ------------------------------------------------------------------------

    [INFO] Compilation failure

    SingleLevelGroupByReportExtended.java:[90,37] cannot find symbol

    symbol  : constructor OneDimensionalDocIssueHitCollector(java.lang.String,com.atlassian.jira.issue.statistics.StatsGroup,org.apache.lucene.index.IndexReader,com.atlassian.jira.issue.IssueFactory,com.atlassian.jira.web.FieldVisibilityManager,com.atlassian.jira.issue.search.ReaderCache) location: class com.atlassian.jira.issue.statistics.util.OneDimensionalDocIssueHitCollector

    Thanks

  11. Anonymous

    I am also getting the error "The constructor OneDimensionalDocIssueHitCollector(String, StatsGroup, IndexReader, IssueFactory, FieldVisibilityManager, ReaderCache) is undefined" as others above has posted. It looks like two new parameters were added to OneDimensionalDocIssueHitCollector FieldManager and ProjectManager but unsure how to set those properly.

    A lot of the example code is using classes that have been deprecated as well.