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:
- Single Level Group By Report Extended.
- 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:
- Java classes encapsulating the app logic.
- Resources for display of the app user interface (UI).
- 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:
- 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.
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.
- Set up the Atlassian Plugin SDK and build a project if you have not done it yet.
-
Navigate to the directory where you want to keep the app project and run the following SDK command:
1
atlas-create-jira-plugin
-
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
-
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.
-
Navigate to the directory created by SDK.
-
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/
-
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.
- In the root folder of your app, open the
pom.xml
file. -
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>
-
Update the project
description
element as follows:1
<description>Extends Jira issue reports.</description>
-
Remove the commenting from around the
dependency
element for thejira-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. -
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.
-
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:
- Open a Terminal and navigate to the app root folder where the
pom.xml
is located. - Run
atlas-create-jira-plugin-module
command. - Enter the number for the
Report
module. -
When prompted, enter the following.
Enter New Classname
SingleLevelGroupByReportExtended
Package Name
com.atlassian.plugins.tutorial.jira.reports
-
Select
N
for Show Advanced Setup. - Select
Y
for Add Another Plugin Module. - Enter the number for the
Report
module again. -
Enter the following.
Enter New Classname
CreationReport
Package Name
com.atlassian.plugins.tutorial.jira.reports
-
Select
N
for Show Advanced Setup. - Select
N
for Add Another Plugin Module. - 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.
-
Navigate to
src/main/resources
and open theatlassian-plugin.xml
file. -
Under the
Single Level Group By Report Extended
module, uncomment theproperties
element and replace the defaultproperty
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.
-
Under the
Creation Report
module, uncomment and replace theproperties
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:
-
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 Jirafilterprojectpicker
facility. -
startDate
— sets the start of the time period included in the report. -
endDate
— sets the end of the time period in the report. -
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 .
-
Under the project home at
src/main/java/com/atlassian/plugins/tutorial/jira/reports
, open theSingleLevelGroupByReportExtended.java
file. -
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.
-
Under the existing field declarations for the class, add a new field.
1
private final DateTimeFormatter formatter;
-
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(); ...
-
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.
-
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.
- Navigate to
src/main/resources/templates/reports/single-level-group-by-report-extended
and open theview.vm
file. -
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