Skip to end of metadata
Go to start of metadata

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

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

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:

 

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
  • No labels