banner



How To Create A Report In Jira

Creating a Jira report

Applicable:

This tutorial applies to Jira 7.1.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.

Overview of the tutorial

This tutorial shows you how to create custom Jira reports. In this tutorial, you'll add two reports:

  1. Single Level Group By Report Extended.
  2. Creation Report.

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

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

Notice that Assignee and Updated fields appear in the output.

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

Your completed app will consist of the following components:

  1. Java classes encapsulating the app logic.
  2. Resources for display of the app user interface (UI).
  3. An app descriptor to present the app UI in Jira.

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

About these instructions

You can use any supported combination of operating system and IDE to create this app. These instructions were written using IntelliJ IDEA 2017.3 on macOS Sierra. If you use another operating system or IDE combination, you should use the equivalent operations for your specific environment.

This tutorial was last tested with Jira 7.7.1 using the Atlassian SDK 6.3.10.

Before you begin

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

  1. The basics of Java development: classes, interfaces, methods, how to use the compiler, and so on.
  2. How to create an Atlassian plugin project using the Atlassian Plugin SDK.
  3. The basics of using and administering Jira.
  4. This tutorial also involves creating Apache Velocity templates. To extend the tutorial code, you should have a good handle on how Velocity templates work.

App 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 app source code on Atlassian Bitbucket.

To clone the repository, run the following command:

1
            git clone https://atlassian_tutorial@bitbucket.org/atlassian_tutorial/jira-report-plugin.git          

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

Step 1. Create the app project

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

  1. Set up the Atlassian Plugin SDK and build a project if you have not done it yet.
  2. Navigate to the directory where you want to keep the app project and run the following SDK command:

    1
                    atlas-create-jira-plugin              
  3. To identify your app, enter the following information.

    group-id

    com.atlassian.plugins.tutorial.jira

    artifact-id

    jira-report-plugin

    version

    1.0-SNAPSHOT

    package

    com.atlassian.plugins.tutorial.jira

  4. 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.

  5. Navigate to the directory created by SDK.

  6. Delete the test directories.

    Setting up testing for your app isn't part of this tutorial. Run the following commands to delete the generated test skeleton:

    1 2
                    rm -rf ./src/test/java rm -rf ./src/test/resources/              
  7. Delete the unneeded Java class files.

    1
                    rm -rf ./src/main/java/com/atlassian/plugins/tutorial/jira/*              

Step 2. Review and tweak the generated stub code

It's a good idea to familiarize yourself with the project configuration file (that is pom.xml ) and resource files. In this section, you will review and tweak the pom.xml file and the app descriptor file.

Open your app project in your favorite IDE and follow instructions in the next sections.

Add app metadata to the POM

The POM is located at the root of your project and declares the project dependencies and other information. In this step you add metadata about your app and your company or organization to the file.

  1. In the root folder of your app, open the pom.xml file.
  2. Add your company or organization name and website URL to the organization element.

    1 2 3 4
                    <organization>     <name>Example Company</name>     <url>http://www.example.com/</url> </organization>              
  3. Update the project description element as follows:

    1
                    <description>Extends Jira issue reports.</description>              
  4. Remove the commenting from around the dependency element for the jira-core artifact, this will include dependency 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 app descriptor

Your stub code contains an app descriptor file atlassian-plugin.xml . This is an XML file that identifies the app to the host application (Jira) and defines the required app functionality.

  1. Under the src/main/resources directory of your project home, open the descriptor file.

    You should see something like this (comments removed):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
            <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>     <resource type="i18n" name="i18n" location="jira-report-plugin"/>     <web-resource key="jira-report-plugin-resources" name="jira-report-plugin Web Resources">         <dependency>com.atlassian.auiplugin:ajs</dependency>         <resource type="download" name="jira-report-plugin.css" location="/css/jira-report-plugin.css"/>         <resource type="download" name="jira-report-plugin.js" location="/js/jira-report-plugin.js"/>         <resource type="download" name="images/" location="/images"/>         <context>jira-report-plugin</context>     </web-resource> </atlassian-plugin>          

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 app. For your modules, add two report modules as follows:

  1. Open a Terminal and navigate to the app root folder where the pom.xml is located.
  2. Run atlas-create-jira-plugin-module command.
  3. Enter the number for the Report module.
  4. When prompted, enter the following.

    Enter New Classname

    SingleLevelGroupByReportExtended

    Package Name

    com.atlassian.plugins.tutorial.jira.reports

  5. Select N for Show Advanced Setup.

  6. Select Y for Add Another Plugin Module.
  7. Enter the number for the Report module again.
  8. Enter the following.

    Enter New Classname

    CreationReport

    Package Name

    com.atlassian.plugins.tutorial.jira.reports

  9. Select N for Show Advanced Setup.

  10. Select N for Add Another Plugin Module.
  11. Confirm your selections.

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

Step 4. Add module properties

Report module properties are the configurable fields that the app exposes in the Jira UI. We'll add a few properties to the module definition.

  1. Navigate to src/main/resources and open the atlassian-plugin.xml file.

  2. Under the Single Level Group By Report Extended module, uncomment the properties element and replace the default property elements it contains with the following:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
                    ``` xml <report name="Single Level Group By Report Extended"...    ...    <properties>          <property>              <key>filterid</key>              <name>report.singlelevelgroupby.filterId</name>              <description>report.singlelevelgroupby.filterId.description</description>              <type>filterpicker</type>              <i18n>false</i18n>          </property>          <property>              <key>mapper</key>              <name>report.singlelevelgroupby.mapper</name>              <description>report.singlelevelgroupby.mapper.description</description>              <type>select</type>              <values class="com.atlassian.jira.issue.statistics.FilterStatisticsValuesGenerator" />          </property>     </properties> ```  We're adding two new properties: a filter picker and a selector for the statistics type by which to group the result.              
  3. Under the Creation Report module, uncomment and replace the properties element with the following:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
                    <report name="Creation Report"...    ...    <properties>          <property>              <key>projectid</key>              <name>report.issuecreation.projectid.name</name>              <description>report.issuecreation.projectid.description</description>              <type>filterprojectpicker</type>          </property>          <property>              <key>startDate</key>              <name>report.issuecreation.startdate</name>              <description>report.issuecreation.startdate.description</description>              <type>date</type>          </property>          <property>              <key>endDate</key>              <name>report.issuecreation.enddate</name>              <description>report.issuecreation.enddate.description</description>              <type>date</type>          </property>          <property>              <key>interval</key>              <name>report.issuecreation.interval</name>              <description>report.issuecreation.interval.description</description>              <type>long</type>              <default>3</default>          </property>    </properties>                              

    Here we've added the following:

    1. 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 filterprojectpicker facility.
    2. startDate — sets the start of the time period included in the report.
    3. endDate — sets the end of the time period in the report.
    4. interval — specifies the time interval used to divide the overall time period. In other words, this is the histogram interval.

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

Step 5. Write the Single Level Group By Report Extended 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 extend a report delivered with Jira, the SingleLevelGroupByReport class. If you have an access to the Jira source code, you can find the source code for the original at this location:

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

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 and time format configured in Jira.

The view template gets values to display in Jira using the parameter map that is passed by the module code. So, to add the DateTimeFormatter object to the Velocity template, we'll modify the parameter map generated by the original Jira report .

  1. Under the project home at src/main/java/com/atlassian/plugins/tutorial/jira/reports , open the SingleLevelGroupByReportExtended.java file.

  2. Replace its contents with the following:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
                    package com.atlassian.plugins.tutorial.jira.reports;  import com.atlassian.jira.bc.JiraServiceContext; import com.atlassian.jira.bc.JiraServiceContextImpl; import com.atlassian.jira.bc.filter.SearchRequestService; import com.atlassian.jira.bc.issue.search.SearchService; import com.atlassian.jira.datetime.DateTimeFormatter; import com.atlassian.jira.datetime.DateTimeFormatterFactory; import com.atlassian.jira.datetime.DateTimeStyle; import com.atlassian.jira.exception.PermissionException; import com.atlassian.jira.issue.CustomFieldManager; import com.atlassian.jira.issue.IssueFactory; import com.atlassian.jira.issue.fields.FieldManager; import com.atlassian.jira.issue.index.IssueIndexManager; import com.atlassian.jira.issue.search.ReaderCache; import com.atlassian.jira.issue.search.SearchException; import com.atlassian.jira.issue.search.SearchProvider; import com.atlassian.jira.issue.search.SearchRequest; import com.atlassian.jira.issue.statistics.FilterStatisticsValuesGenerator; import com.atlassian.jira.issue.statistics.StatisticsMapper; import com.atlassian.jira.issue.statistics.StatsGroup; import com.atlassian.jira.issue.statistics.util.OneDimensionalDocIssueHitCollector; import com.atlassian.jira.plugin.report.impl.AbstractReport; import com.atlassian.jira.project.ProjectManager; import com.atlassian.jira.user.ApplicationUser; import com.atlassian.jira.util.SimpleErrorCollection; import com.atlassian.jira.web.FieldVisibilityManager; import com.atlassian.jira.web.action.ProjectActionSupport; import com.atlassian.jira.web.bean.PagerFilter; import com.atlassian.plugin.spring.scanner.annotation.component.Scanned; import com.atlassian.plugin.spring.scanner.annotation.imports.JiraImport; import com.atlassian.util.profiling.UtilTimerStack; import com.google.common.collect.ImmutableMap; import com.opensymphony.util.TextUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.apache.lucene.search.Collector;  import java.util.Arrays; import java.util.Map;  @Scanned public class SingleLevelGroupByReportExtended extends AbstractReport {     private static final Logger log = Logger.getLogger(SingleLevelGroupByReportExtended.class);      @JiraImport     private final SearchProvider searchProvider;     @JiraImport     private final SearchRequestService searchRequestService;     @JiraImport     private final IssueFactory issueFactory;     @JiraImport     private final CustomFieldManager customFieldManager;     @JiraImport     private final IssueIndexManager issueIndexManager;     @JiraImport     private final SearchService searchService;     @JiraImport     private final FieldVisibilityManager fieldVisibilityManager;     @JiraImport     private final FieldManager fieldManager;     @JiraImport     private final ProjectManager projectManager;     @JiraImport     private final ReaderCache readerCache;      public SingleLevelGroupByReportExtended(final SearchProvider searchProvider,                                             final SearchRequestService searchRequestService, final IssueFactory issueFactory,                                             final CustomFieldManager customFieldManager, final IssueIndexManager issueIndexManager,                                             final SearchService searchService, final FieldVisibilityManager fieldVisibilityManager,                                             final ReaderCache readerCache,                                             final FieldManager fieldManager,                                             final ProjectManager projectManager) {         this.searchProvider = searchProvider;         this.searchRequestService = searchRequestService;         this.issueFactory = issueFactory;         this.customFieldManager = customFieldManager;         this.issueIndexManager = issueIndexManager;         this.searchService = searchService;         this.fieldVisibilityManager = fieldVisibilityManager;         this.readerCache = readerCache;         this.fieldManager = fieldManager;         this.projectManager = projectManager;     }      public StatsGroup getOptions(SearchRequest sr, ApplicationUser user, StatisticsMapper mapper) throws PermissionException {         try {             return searchMapIssueKeys(sr, user, mapper);         } catch (SearchException e) {             log.error("Exception rendering " + this.getClass().getName() + ".  Exception \n" + Arrays.toString(e.getStackTrace()));             return null;         }     }      public StatsGroup searchMapIssueKeys(SearchRequest request, ApplicationUser searcher, StatisticsMapper mapper) throws SearchException {         try {             UtilTimerStack.push("Search Count Map");             StatsGroup statsGroup = new StatsGroup(mapper);             Collector hitCollector = new OneDimensionalDocIssueHitCollector(mapper.getDocumentConstant(), statsGroup,                     issueIndexManager.getIssueSearcher().getIndexReader(), issueFactory,                     fieldVisibilityManager, readerCache, fieldManager, projectManager);             searchProvider.searchAndSort((request != null) ? request.getQuery() : null, searcher, hitCollector, PagerFilter.getUnlimitedFilter());             return statsGroup;         } finally {             UtilTimerStack.pop("Search Count Map");         }     }      public String generateReportHtml(ProjectActionSupport action, Map params) throws Exception {          String filterId = (String) params.get("filterid");         if (filterId == null) {             log.info("Single Level Group By Report run without a project selected (JRA-5042): params=" + params);             return "<span class='errMsg'>No search filter has been selected. Please "                     + "<a href=\"IssueNavigator.jspa?reset=Update&pid="                     + TextUtils.htmlEncode((String) params.get("selectedProjectId"))                     + "\">create one</a>, and re-run this report.";         }         String mapperName = (String) params.get("mapper");         final StatisticsMapper mapper = new FilterStatisticsValuesGenerator().getStatsMapper(mapperName);         final JiraServiceContext ctx = new JiraServiceContextImpl(action.getLoggedInUser());         final SearchRequest request = searchRequestService.getFilter(ctx, new Long(filterId));          try {             final Map startingParams = ImmutableMap.builder()                     .put("action", action)                     .put("statsGroup", getOptions(request, action.getLoggedInUser(), mapper))                     .put("searchRequest", request)                     .put("mapperType", mapperName)                     .put("customFieldManager", customFieldManager)                     .put("fieldVisibility", fieldVisibilityManager)                     .put("searchService", searchService)                     .put("portlet", this).build();              return descriptor.getHtml("view", startingParams);          } catch (PermissionException e) {             log.error(e.getStackTrace());             return null;         }     }      public void validate(ProjectActionSupport action, Map params) {         super.validate(action, params);         String filterId = (String) params.get("filterid");         if (StringUtils.isEmpty(filterId)) {             action.addError("filterid", action.getText("report.singlelevelgroupby.filter.is.required"));         } else {             validateFilterId(action, filterId);         }     }      private void validateFilterId(ProjectActionSupport action, String filterId) {         try {             JiraServiceContextImpl serviceContext = new JiraServiceContextImpl(                     action.getLoggedInUser(), new SimpleErrorCollection());             SearchRequest searchRequest = searchRequestService.getFilter(serviceContext, new Long(filterId));             if (searchRequest == null) {                 action.addErrorMessage(action.getText("report.error.no.filter"));             }         } catch (NumberFormatException nfe) {             action.addError("filterId", action.getText("report.error.filter.id.not.a.number", filterId));         }     } }              

    This is simply the original report with Atlassian Spring Scanner annotations. Next, we'll 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.

    1
                    private final DateTimeFormatter formatter;              
  4. Add the new field as a parameter passed to the class constructor.

    1 2 3 4 5
                    public SingleLevelGroupByReportExtended( ...                                           @JiraImport DateTimeFormatterFactory dateTimeFormatterFactory )    ...    this.formatter = dateTimeFormatterFactory.formatter().withStyle(DateTimeStyle.DATE).forLoggedInUser();    ...              
  5. In the generateReportHtml() method, add the following line:

    1 2 3 4
                    startingParams               ...               .put("formatter", formatter).build(); return descriptor.getHtml("view", startingParams);              

    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 modified class should look something like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
            package com.atlassian.plugins.tutorial.jira.reports;  import com.atlassian.jira.bc.JiraServiceContext; import com.atlassian.jira.bc.JiraServiceContextImpl; import com.atlassian.jira.bc.filter.SearchRequestService; import com.atlassian.jira.bc.issue.search.SearchService; import com.atlassian.jira.datetime.DateTimeFormatter; import com.atlassian.jira.datetime.DateTimeFormatterFactory; import com.atlassian.jira.datetime.DateTimeStyle; import com.atlassian.jira.exception.PermissionException; import com.atlassian.jira.issue.CustomFieldManager; import com.atlassian.jira.issue.IssueFactory; import com.atlassian.jira.issue.fields.FieldManager; import com.atlassian.jira.issue.index.IssueIndexManager; import com.atlassian.jira.issue.search.ReaderCache; import com.atlassian.jira.issue.search.SearchException; import com.atlassian.jira.issue.search.SearchProvider; import com.atlassian.jira.issue.search.SearchRequest; import com.atlassian.jira.issue.statistics.FilterStatisticsValuesGenerator; import com.atlassian.jira.issue.statistics.StatisticsMapper; import com.atlassian.jira.issue.statistics.StatsGroup; import com.atlassian.jira.issue.statistics.util.OneDimensionalDocIssueHitCollector; import com.atlassian.jira.plugin.report.impl.AbstractReport; import com.atlassian.jira.project.ProjectManager; import com.atlassian.jira.user.ApplicationUser; import com.atlassian.jira.util.SimpleErrorCollection; import com.atlassian.jira.web.FieldVisibilityManager; import com.atlassian.jira.web.action.ProjectActionSupport; import com.atlassian.jira.web.bean.PagerFilter; import com.atlassian.plugin.spring.scanner.annotation.component.Scanned; import com.atlassian.plugin.spring.scanner.annotation.imports.JiraImport; import com.atlassian.util.profiling.UtilTimerStack; import com.google.common.collect.ImmutableMap; import com.opensymphony.util.TextUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.apache.lucene.search.Collector;  import java.util.Arrays; import java.util.Map;  @Scanned public class SingleLevelGroupByReportExtended extends AbstractReport {     private static final Logger log = Logger.getLogger(SingleLevelGroupByReportExtended.class);      @JiraImport     private final SearchProvider searchProvider;     @JiraImport     private final SearchRequestService searchRequestService;     @JiraImport     private final IssueFactory issueFactory;     @JiraImport     private final CustomFieldManager customFieldManager;     @JiraImport     private final IssueIndexManager issueIndexManager;     @JiraImport     private final SearchService searchService;     @JiraImport     private final FieldVisibilityManager fieldVisibilityManager;     @JiraImport     private final FieldManager fieldManager;     @JiraImport     private final ProjectManager projectManager;     @JiraImport     private final ReaderCache readerCache;     private final DateTimeFormatter formatter;      public SingleLevelGroupByReportExtended(final SearchProvider searchProvider,                                             final SearchRequestService searchRequestService, final IssueFactory issueFactory,                                             final CustomFieldManager customFieldManager, final IssueIndexManager issueIndexManager,                                             final SearchService searchService, final FieldVisibilityManager fieldVisibilityManager,                                             final ReaderCache readerCache,                                             final FieldManager fieldManager,                                             final ProjectManager projectManager,                                             @JiraImport DateTimeFormatterFactory dateTimeFormatterFactory) {         this.searchProvider = searchProvider;         this.searchRequestService = searchRequestService;         this.issueFactory = issueFactory;         this.customFieldManager = customFieldManager;         this.issueIndexManager = issueIndexManager;         this.searchService = searchService;         this.fieldVisibilityManager = fieldVisibilityManager;         this.readerCache = readerCache;         this.fieldManager = fieldManager;         this.projectManager = projectManager;         this.formatter = dateTimeFormatterFactory.formatter().withStyle(DateTimeStyle.DATE).forLoggedInUser();     }      public StatsGroup getOptions(SearchRequest sr, ApplicationUser user, StatisticsMapper mapper) throws PermissionException {         try {             return searchMapIssueKeys(sr, user, mapper);         } catch (SearchException e) {             log.error("Exception rendering " + this.getClass().getName() + ".  Exception \n" + Arrays.toString(e.getStackTrace()));             return null;         }     }      public StatsGroup searchMapIssueKeys(SearchRequest request, ApplicationUser searcher, StatisticsMapper mapper) throws SearchException {         try {             UtilTimerStack.push("Search Count Map");             StatsGroup statsGroup = new StatsGroup(mapper);             Collector hitCollector = new OneDimensionalDocIssueHitCollector(mapper.getDocumentConstant(), statsGroup,                     issueIndexManager.getIssueSearcher().getIndexReader(), issueFactory,                     fieldVisibilityManager, readerCache, fieldManager, projectManager);             searchProvider.searchAndSort((request != null) ? request.getQuery() : null, searcher, hitCollector, PagerFilter.getUnlimitedFilter());             return statsGroup;         } finally {             UtilTimerStack.pop("Search Count Map");         }     }      public String generateReportHtml(ProjectActionSupport action, Map params) throws Exception {          String filterId = (String) params.get("filterid");         if (filterId == null) {             log.info("Single Level Group By Report run without a project selected (JRA-5042): params=" + params);             return "<span class='errMsg'>No search filter has been selected. Please "                     + "<a href=\"IssueNavigator.jspa?reset=Update&pid="                     + TextUtils.htmlEncode((String) params.get("selectedProjectId"))                     + "\">create one</a>, and re-run this report.";         }         String mapperName = (String) params.get("mapper");         final StatisticsMapper mapper = new FilterStatisticsValuesGenerator().getStatsMapper(mapperName);         final JiraServiceContext ctx = new JiraServiceContextImpl(action.getLoggedInUser());         final SearchRequest request = searchRequestService.getFilter(ctx, new Long(filterId));          try {             final Map startingParams = ImmutableMap.builder()                     .put("action", action)                     .put("statsGroup", getOptions(request, action.getLoggedInUser(), mapper))                     .put("searchRequest", request)                     .put("mapperType", mapperName)                     .put("customFieldManager", customFieldManager)                     .put("fieldVisibility", fieldVisibilityManager)                     .put("searchService", searchService)                     .put("portlet", this)                     .put("formatter", formatter).build();              return descriptor.getHtml("view", startingParams);          } catch (PermissionException e) {             log.error(e.getStackTrace());             return null;         }     }      public void validate(ProjectActionSupport action, Map params) {         super.validate(action, params);         String filterId = (String) params.get("filterid");         if (StringUtils.isEmpty(filterId)) {             action.addError("filterid", action.getText("report.singlelevelgroupby.filter.is.required"));         } else {             validateFilterId(action, filterId);         }     }      private void validateFilterId(ProjectActionSupport action, String filterId) {         try {             JiraServiceContextImpl serviceContext = new JiraServiceContextImpl(                     action.getLoggedInUser(), new SimpleErrorCollection());             SearchRequest searchRequest = searchRequestService.getFilter(serviceContext, new Long(filterId));             if (searchRequest == null) {                 action.addErrorMessage(action.getText("report.error.no.filter"));             }         } catch (NumberFormatException nfe) {             action.addError("filterId", action.getText("report.error.filter.id.not.a.number", filterId));         }     } }          

Step 6. Create the view template for the report

In this step, we 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. Navigate to src/main/resources/templates/reports/single-level-group-by-report-extended and open the view.vm file.
  2. Replace the placeholder content with the following:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
                    #enable_html_escaping() #if ($searchRequest)     #set ($urlPrefix = "${req.contextPath}/secure/IssueNavigator.jspa?reset=true") 
    

0 Response to "How To Create A Report In Jira"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel