Search Request View Plugin Module

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.

On this page:

The Search Request View Plugin Module

A search request view plugin consists of 3 components:

  • The plugin module definition in atlassian-plugin.xml
  • The view implementation class defining any logic needed to render a view
  • Any number of view templates defining how to render the view.

Let's attempt a sample implementation, to render the results of a search request in this format:

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

Plugin Module Definition

First a definition of the plugin is needed in your plugin's atlassian-plugin.xml:

...

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

Search Request View Implementation Class

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

/**
 * 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);
        }
    }
}

View templates

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
#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
    <issue>
        <key>$!issue.key</key>
        <summary>$!issue.summary</summary>
    </issue>

Finally the footer should close the <issues> tag:

searchrequest-xml-footer.vm
</issues>

Limiting the number of search results shown

While JIRA provides an application property to limit the number of issues returned from its built-in search request views (see Limiting the number of issues returned from a search view such as an RSS feed), this limit is not automatically applied to plugin search views accessed from the issue navigator.

For JIRA 4.3.0 and later:

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:

public 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));

}
For JIRA 4.2.x and earlier:

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.

...

{

    ...

    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));

}

Example

The full source for the sample plugin above can be downloaded here. If you wish to just try the plugin out feel free to download the plugin jar.

Here's what the sample plugin looks like in action:

And this is a sample search result rendered by the plugin:

Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport