Available: | Search Request View plugin modules are available in JIRA 3.7 and later. |
Search request view plugin modules are used to display different representations of search results in the issue navigator. They will be displayed as a link at the top of the issue navigator. Once clicked, JIRA will render the search request view.
Here's what the bundled 'Printable' view looks like:
Search request views can be used to render the search results in any possible representation. Some popuplar choices are for example RSS or XML.
A search request view plugin consists of 3 components:
Let's attempt a sample implementation, to render the results of a search request in this format:
1 2<issues filtername="My filter"> <issue> <key>HSP-1</key> <summary>Sample issue</summary> </issue> <issue> <key>MKY-1<key> <summary>Another sample issue</summary> </issue> </issues>
First a definition of the plugin is needed in your plugin's atlassian-plugin.xml
:
1 2... <search-request-view key="simple-searchrequest-xml" name="Simple XML" class="com.atlassian.jira.sample.searchrequest.SimpleSearchRequestXmlView" state='enabled' fileExtension="xml" contentType="text/xml"> <resource type="velocity" name="header" location="templates/searchrequest-xml-header.vm"/> <resource type="velocity" name="singleissue" location="templates/searchrequest-xml-singleissue.vm"/> <resource type="velocity" name="footer" location="templates/searchrequest-xml-footer.vm"/> <order>100</order> </search-request-view> ...
The search-request-view module is fairly straight-forward. It defines a unique key for the module, a name which will be used for the link in the issue navigator and the view implementation class. Further it also defines what contentType to return when displaying this view and a number of view resources. Finally the order can be used to control in which order the links appear in the IssueNavigator.
The view implementation class below extends a convenience class available in JIRA to make writing search request views easier, namely AbstractSearchRequestView. All that's left to do then is to implement the writeSearchResults()
method.
If a search request view needs more control (e.g. control the HTTP headers used in the returned view), then an implementation class can implement the SearchRequestView interface directly.
SimpleSearchRequestXmlView.java
1 2... /** * Sample implementation of a simple XML search request view. * <p/> * Note that this class extends {@link com.atlassian.jira.plugin.searchrequestview.AbstractSearchRequestView}. This * isn't necessary but makes things a lot simpler. It is also possible to implement the {@link * com.atlassian.jira.plugin.searchrequestview.SearchRequestView} interface directly. */ public class SimpleSearchRequestXmlView extends AbstractSearchRequestView { private final JiraAuthenticationContext authenticationContext; private final SearchProviderFactory searchProviderFactory; private final IssueFactory issueFactory; private final SearchProvider searchProvider; public SimpleSearchRequestXmlView(JiraAuthenticationContext authenticationContext, SearchProviderFactory searchProviderFactory, IssueFactory issueFactory, SearchProvider searchProvider) { this.authenticationContext = authenticationContext; this.searchProviderFactory = searchProviderFactory; this.issueFactory = issueFactory; this.searchProvider = searchProvider; } public void writeSearchResults(final SearchRequest searchRequest, final SearchRequestParams searchRequestParams, final Writer writer) { final Map defaultParams = JiraVelocityUtils.getDefaultVelocityParams(authenticationContext); //Need to put the filtername into the velocity context. This may be null if this is an anoymous filter. final Map headerParams = new HashMap(defaultParams); headerParams.put("filtername", searchRequest.getName()); try { //First we need to write the header writer.write(descriptor.getHtml("header", headerParams)); //now lets write the search results. This basically iterates over each issue in the search results and writes //it to the writer using the format defined by this plugin. To ensure that this doesn't result in huge //memory consumption only one issue should be loaded into memory at a time. This can be guaranteed by using a //Hitcollector. final Searcher searcher = searchProviderFactory.getSearcher(SearchProviderFactory.ISSUE_INDEX); final Map issueParams = new HashMap(defaultParams); //This hit collector is responsible for writing out each issue as it is encountered in the search results. //It will be called for each search result by the underlying Lucene search code. final DocumentHitCollector hitCollector = new IssueWriterHitCollector(searcher, writer, issueFactory) { protected void writeIssue(Issue issue, Writer writer) throws IOException { //put the current issue into the velocity context and render the single issue view issueParams.put("issue", issue); writer.write(descriptor.getHtml("singleissue", issueParams)); } }; //now run the search that's defined in the issue navigator and pass in the hitcollector from above which will //write out each issue in the format specified in this plugin. searchProvider.searchAndSort(searchRequest.getQuery(), authenticationContext.getUser(), hitCollector, searchRequestParams.getPagerFilter()); //finally lets write the footer. writer.write(descriptor.getHtml("footer", Collections.emptyMap())); } catch (IOException e) { throw new RuntimeException(e); } catch (SearchException e) { throw new RuntimeException(e); } } }
Finally, a number of view templates are needed to display our new view.
First, the header needs to display the filtername as an attribute of the <issues>
tag or simply 'Anonymous' if the view isn't displaying a saved filter:
searchrequest-xml-header.vm
1 2#set($displayName = 'Anonymous') #if($filtername) #set($displayName = $textutils.htmlEncode($filtername)) #end <issues filtername="$displayName">
Then to render each issue, we need to construct an <issue>
tag for each individual issue:
searchrequest-xml-singleissue.vm
1 2<issue> <key>$!issue.key</key> <summary>$!issue.summary</summary> </issue>
Finally the footer should close the <issues>
tag:
searchrequest-xml-footer.vm
1 2</issues>
While JIRA provides an application property to limit the number of issues returned from its built-in search request views , this limit is not automatically applied to plugin search views accessed from the issue navigator.
You can get the value of this property using the getMaxResults() method of the IssueSearchLimits
interface, after this interface has been injected into your constructor. For example:
1 2public SimpleSearchRequestXmlView(JiraAuthenticationContext authenticationContext, SearchProviderFactory searchProviderFactory, IssueFactory issueFactory, SearchProvider searchProvider, IssueSearchLimits issueSearchLimits) { this.authenticationContext = authenticationContext; this.searchProviderFactory = searchProviderFactory; this.issueFactory = issueFactory; this.searchProvider = searchProvider; this.issueSearchLimits = issueSearchLimits; } ... { ... int maxIssues = issueSearchLimits.getMaxResults(); ... // Actually do the search, limiting the size instead of getting all search results: searchProvider.searchAndSort(searchRequest.getQuery(), authenticationContext.getUser(), hitCollector, new PagerFilter(maxIssues)); }
Use the getDefaultBackedString() method of the ApplicationProperties
interface to obtain the JIRA_SEARCH_VIEWS_DEFAULT_MAX property of the APKeys
interface, after the ApplicationProperties
interface has been injected into your constructor.
1 2... { ... final String defaultMax = applicationProperties.getDefaultBackedString(APKeys.JIRA_SEARCH_VIEWS_DEFAULT_MAX); int maxIssues = DEFAULT_MAX_RESULTS; if (!StringUtils.isBlank(defaultMax)) { maxIssues = Integer.valueOf(defaultMax); } ... // Actually do the search, limiting the size instead of getting all search results: searchProvider.searchAndSort(searchRequest.getQuery(), authenticationContext.getUser(), hitCollector, new PagerFilter(maxIssues)); }
Here's what the sample plugin looks like in action:
And this is a sample search result rendered by the plugin:
Rate this page: