Access Keys:
Skip to content (Access Key - 0)

<< Plugins Blog

How to create highly customisable lists in Confluence with Advanced Search plugin configs

Aug 05, 2011 14:39

On a regular basis via this blog, I'm going to walk you through a useful aspect of one of our Confluence Plugins. This time, I'm going to focus on the Advanced Search plugin, and its configurations functionality in particular. You will need to be experienced with Confluence and comfortable playing about with code to follow this topic.

The Advanced Search plugin is one of our most popular plugins. It's available for free as part of our Confluence Essentials Plugin Pack. The plugin allows you to query Confluence's search index directly, so that you can create lists of results efficiently. For example, you could show a list of blogs with specific labels, created in a particular space within a set date range. Now you could display such a list with a specific plugin or macro, however, they often don't perform well on large Confluence instances, or on high-traffic pages, and how the results are displayed is often set by the macro's author, which may not fit in with your Confluence page layout or theme.

Most people who use the Advanced Search plugin just use it for the search form macros. These macros allow you to create and display the results from custom searches within a Confluence page. However, Advanced Search configurations allow you to take the level of customisation up a notch, giving you almost complete control of your search queries and how the results are displayed and what is shown.

 

Using a config

You can add Advanced Search configurations to Confluence by selecting the 'Advanced Search Configurations' menu item from the Administration section of the Site Admin console. You embed the results from an Advanced Search config in a page by using the macro 'advanced-search' and specifying the config ID such as

{advanced-search:id=questionConfig}

Anatomy of a config

The configuration I'm using displays up to 5 pages that have the label 'question', created in the 'community' space, ordered by their creation date. Results show the page's title, the date it was last modified and who created it along with their profile picture. By understanding how this config works, you can create your own entirely customisable lists. The complete config and the result it produces are included at the end of this blog post.

The two main parts of a config which I'm going to cover are the preSearch and output sections, written in Jython and Velocity respectively. Knowledge of those aren't required though as I'll go through an example configuration, explaining the important details so that you can customise it and re-use it for yourself.

Here are the important parts:

Creating your search query

<config id="questionConfig" title="Questions">

The id is a unique identifier that you set to distinguish that config from others, and refer to it when using the advanced-search macro. The title is displayed alongside the id on the Configs screen - it's a good idea to set a descriptive title to help you remember what the configuration does when returning to it later.

labelSubQuery = searcher.buildStandardQuery(["labelText"], "question")
bean.getLuceneQuery().add( labelSubQuery, org.apache.lucene.search.BooleanClause.Occur.MUST )

This starts to create our search query, in this case it searches for the label 'question'. However, you can specify another label by replacing the value 'question' with another label. You can add the requirement for multiple labels to be found by including additional labelSubQueries e.g.

labelSubQuery = searcher.buildStandardQuery(["labelText"], "question")
bean.getLuceneQuery().add( labelSubQuery, org.apache.lucene.search.BooleanClause.Occur.MUST )
labelSubQuery = searcher.buildStandardQuery(["labelText"], "report")
bean.getLuceneQuery().add( labelSubQuery, org.apache.lucene.search.BooleanClause.Occur.MUST )

This would require both labels 'question' and 'report' to exist on a page. You can also change the condition, by changing the 'BooleanClause.Occur' value from MUST to SHOULD or MUST_NOT. You can also search by other means, such as by space type or even results from your favourite Confluence users. For example, the following code searches across all spaces with the space type 'personal', which is an efficient way to search across people's personal blogs for example.

personalSubQuery = searcher.buildStandardQuery(["space.type"], "personal")
bean.getLuceneQuery().add( personalSubQuery, org.apache.lucene.search.BooleanClause.Occur.MUST )

Limiting your results

You can specify how the results are sorted, by adding a sort field. The following example lists results by the date they were initially created, but you might want to list them by their modified date (change 'created' to 'modified'). You can experiment with other Confluence Search Fields too. You can add multiple sortField values, and change between ascending/descending by changing the reverse value between true and false.

<sort>
      <sortField reverse="true">created</sortField>
</sort>

You can limit the searches to results from specific spaces by specifying space keys. You can add multiple space values, or remove them to allow searching across all spaces.

<spaceKeys>
	<space>community</space>
</spaceKeys>

The maxResults value here refers to the total number of search results that will be returned, whilst the pageSize value specifies how many results will be displayed on each page. With the following values for example, there will be a maximum of 10 pages of results.

	<maxResults>250</maxResults>
	<pageSize>25</pageSize>

You can also limit results to specific content types. Here just Confluence pages will be returned, but you can also types such as 'blogpost', 'comment', 'attachment' or none at all.

<contentTypes>
	<type>page</type>
</contentTypes>

That covers the elements about what we search for and will be returned, we will now focus on how we want the results to be displayed.

Displaying your search results

We start by checking to see if any results were returned, if none were returned then we display the message 'None have been submitted'

#if ($bean.getPaginationSupport().getItems() && $bean.getPaginationSupport().getPage().size() > 0)
...
#else
<tr>
        <td>
        None have been submitted.
        </td>
</tr>
#end

Assuming results are returned then we iterate through each result with the line

#foreach ($result in $bean.getPaginationSupport().getPage())

For each item we display the page title with '$result.get("title")' and link to it with some other values and HTML

<b><a href="$req.contextPath$result.get('urlPath')">$!result.get("title")</a></b>

You can specify various elements about a page, such as creation date, modification date, who has edited the page etc. See the appendix for more. In this example we also display the date the page was last modified in a nice format such as 'x minutes ago'.

<p>added $action.formatFriendlyDate($result.get("lastModificationDate"))</p>

What's more, you can embed WikiMarkup in your results, by rendering it with '$action.helper.renderConfluenceMacro()'. In our example, we use the following User Macro to display the profile image of the page creator:

#set($image = "{user_profile:" + $result.get('creatorName') + "|showavatar=1}")
$action.helper.renderConfluenceMacro($image)

However, if we wanted we could also have included a Confluence macro such as one from the Reporting Plugin, with a line like the following which displays the number of comments added to the page.

#set($comments = "{report-info:page:all comments>collection:size|source=community:" + $result.get('title') + "}")
$action.helper.renderConfluenceMacro($comments)

We also display who created the document, by getting the author's username and from that getting their full name

<a href="${req.contextPath}/display/~$result.get('creatorName')">$userInfoUtils.getFullName($result.get('creatorName'))</a>

With velocity you can display the results exactly how you want to. You can use code snippets included in some of the shared User Macros to help as well as looking through the links from Atlassian's guide to Velocity.

Finally, to display pagified results, we include the following which displays the page number and next page links. However, since our values for maxResults and pageSize were both 5, it won't include page links in this example.

#set($pageId = $bean.entityId)
#pagination( $bean.paginationSupport "viewpage.action?pageId=$pageId$searchParams" )   

The full config and result

<?xml version="1.0" encoding="UTF-8"?>
<config id="questionConfig" title="Questions"> 
<scripts> 
		<preSearch> 
<![CDATA[ 
import org.apache.lucene.search
from org.apache.lucene.search import BooleanClause 

labelSubQuery = searcher.buildStandardQuery(["labelText"], "question")
bean.getLuceneQuery().add( labelSubQuery, org.apache.lucene.search.BooleanClause.Occur.MUST )
]]> 
		</preSearch> 
	</scripts> 
	<query> 
        <sort>
            <sortField reverse="true">created</sortField>
        </sort>
	<retrieveAllResults>false</retrieveAllResults> 
	<spaceKeys>
		<space>community</space>
	</spaceKeys>
	<maxResults>5</maxResults>
	<pageSize>5</pageSize>
	<contentTypes>
		<type>page</type>
	</contentTypes>
	</query> 
<output> 
<template> 
<![CDATA[ 
<table class="confluenceTable forum forum-table" id="asResults" width="100%">
#if ($bean.getPaginationSupport().getItems() && $bean.getPaginationSupport().getPage().size() > 0)
<tr>
	<th class="confluenceTh topic" width="78%">Topic</th> 
	<th class="confluenceTh author" width="19%">Created By</th> 
</tr> 
#foreach ($result in $bean.getPaginationSupport().getPage())
<tr>
	<td class="confluenceTd topic"><b><a href="$req.contextPath$result.get('urlPath')">$!result.get("title")</a></b> last updated $action.formatFriendlyDate($result.get("lastModificationDate"))
	</td>
	<td class="confluenceTd author">
	#set($image = "{user_profile:" + $result.get('creatorName') + "|showavatar=1}")
	$action.helper.renderConfluenceMacro($image)
	<a href="${req.contextPath}/display/~$result.get('creatorName')">$userInfoUtils.getFullName($result.get('creatorName'))</a></td>
</tr>
#end
#else
<tr>
        <td>
        None have been submitted.
        </td>
</tr>
#end
</table>
#set($pageId = $bean.entityId)
#pagination( $bean.paginationSupport "viewpage.action?pageId=$pageId$searchParams" )    
]]> 
</template> 
</output> 
</config>

and this is what it produces:

Topic Created By
Children macro not being affected by CSS? last updated Feb 19, 2013 Patrick Lim Patrick Lim
sudo plugin - source last updated Feb 15, 2013 webwesen webwesen
Attaching a PDF to a section on the page last updated Feb 15, 2013 Minesh Patel Minesh Patel
Usage of icon parameter in lozenge macro last updated Feb 11, 2013 Sunandita Deb Sunandita Deb
deck and card css last updated Feb 11, 2013 Jordan Jordan

A word of caution

You must get the syntax right for the configs or they very easily break - Jython is particularly picky, requiring the correct indentation and syntax. If you get an error like the following then check your preSearch section over:

If you get the syntax wrong during the output element, then you are likely to get an error such as the following:

Annoyingly it doesn't give you any line reference, so you have to go through all of your code thoroughly to check it is correct.

Getting help

I hope that explaining the key elements of a config has helped you to understand how you might be able to make use of them. To get help and ask questions about your configurations, go to our Community site.

Good luck!

Appendix

The result object, as used in the configuration, has a number of fields that can be presented in the final view (some requiring further processing). The following list details the available fields:

  • handle
  • created
  • linksTo
  • title
  • commenters
  • labelContributions
  • searchLabel
  • versionComment
  • modified
  • realTitle
  • linksFrom
  • spacekey
  • urlPath
  • lasttModifiers
  • contentBody
  • lastModificationDate
  • ancestorIds
  • watchers
  • type
  • editors
  • creationDate
  • content-name-unstemmed
  • lastModifierName
  • space
  • version
  • authorContributions
  • labelText
  • creatorName
  • label
  • content-name-untokenized
Adaptavist Theme Builder Powered by Atlassian Confluence