Faceted navigation customization
The plugin framework allows faceted navigation to be customized in a couple of ways:
- Faceted navigation configuration
-
Include faceted navigation configuration to support a plugin by implementing the
FacetProvider
interface. For example, if you were writing an Instagram custom gather plugin you may wish to provide some Instagram-specific facets as part of the plugin, which are added when the plugin is enabled. - Faceted navigation custom sort
-
Implement a custom sorting algorithm that can be used to sort your faceted navigation categories by implementing a
SearchLifeCycle
plugin that calls a custom comparator.
Provide faceted navigation configuration for a plugin
Interface methods
To provide faceted navigation configuration for a plugin, you need to implement the FacetProvider interface.
The FacetProvider
interface has a single method:
String extraFacetedNavigation(IndexConfigProviderContext context)
Since facets are defined at a results page level, the IndexConfigProviderContext
provides both a collection (parent search package ID) and profile (results page ID), and extraFacetedNavigation
will be run for every results page.
Additional facets can be supplied by returning a JSON similar to the API.
GET /faceted-navigation/v2/collections/{collection}/profiles/{profile}/facet/{id}/views/{view}
This expects to return a list []
of facets. The id
,
lastModified
and created
fields do not need to be set.
When building a plugin, configure the faceted navigation using the faceted navigation configuration screen in the search dashboard. After configuring your facets, copy the JSON from the facet configuration JSON tab on the preview screen for the facet and omit the id , lastModified and created fields.
|
For example:
[{
"name": "Instagram Authors",
"facetValues": "FROM_SCOPED_QUERY_HIDE_UNSELECTED_PARENT_VALUES",
"constraintJoin": "AND",
"selectionType": "SINGLE",
"categories": [{
"type": "MetaDataFieldCategory",
"subCategories": [],
"metadataField": "instagramAuthor"
}],
"order": [ "SELECTED_FIRST", "COUNT_DESCENDING"]
}]
Facet names must be unique within the results page. Facets defined for a results page will be used in preference to facets defined from the plugin if both define a facet with the same name. |
Usage
Facets will automatically start appearing in search results after the plugin is enabled on the results page.
Some facets will require a reindex to take effect. |
Implement a custom sort algorithm for faceted navigation
The sorting of faceted navigation categories is set as part of the facet configuration. The sort option provides a number of sort criteria as well as a custom sort option.
When custom is selected it requires a custom sort comparator to be enabled, and this is implemented in a plugin.
Faceted navigation custom sort is implemented in a search lifecycle plugin that implements a post process re-sorting of the faceted navigation categories within the data model.
Create a faceted navigation custom sort plugin
To create a faceted navigation custom sort plugin:
-
Create a new plugin that implements the search lifecycle plugin interface. This must implement code that reorders the facet categories of the facet that has custom sort enabled.
-
Create a class that implements the custom sort logic using a Java comparator. Call this comparator from the post-process reorder method.
Using a faceted navigation custom sort plugin
-
Enable the plugin that implements the custom sorting
-
Edit your faceted navigation configuration and set the category sorting to custom for the facets that you wish to sort with the plugin.
Funnelback currently only supports the use of a single custom sorting plugin per results page. |
Example: Faceted navigation custom sort comparator
The code below implements a post-process hook that calls a method to reorder the facet categories of the targeted facet.
FacetsCustomSortPositionSearchLifeCyclePlugin.java
package com.funnelback.plugin.facetsCustomSortPosition;
import com.funnelback.publicui.search.model.collection.ServiceConfig;
import com.funnelback.publicui.search.model.transaction.Facet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.funnelback.plugin.SearchLifeCyclePlugin;
import com.funnelback.publicui.search.model.transaction.SearchTransaction;
import java.util.List;
import static com.funnelback.plugin.facetsCustomSortPosition.PluginUtils.*;
public class FacetsCustomSortPositionSearchLifeCyclePlugin implements SearchLifeCyclePlugin {
private static final Logger log = LogManager.getLogger(FacetsCustomSortPositionSearchLifeCyclePlugin.class);
@Override
public void postProcess(SearchTransaction transaction) {
List<Facet> facets = transaction.getResponse().getFacets();
if (facets.isEmpty()) {
log.info("No facet navigation is configured so the plugin will not run.");
return;
}
if (transaction.getQuestion().getCurrentProfileConfig().getConfigKeysWithPrefix(KEY_PREFIX).isEmpty()) {
log.info("No configuration for plugin is provided so the plugin will not run.");
return;
}
reorderFacets(facets, transaction.getQuestion().getCurrentProfileConfig());
}
protected void reorderFacets(List<Facet> facets, ServiceConfig config) {
facets.forEach(facet -> {
if (config.getConfigKeysWithPrefix(KEY_PREFIX + facet.getName()).isEmpty()) {
return;
}
final List<String> first = getConfigKeyValue(config, getConfigKey(facet, KEY_SUFFIX_SORT_ORDER_FIRST));
final List<String> last = getConfigKeyValue(config, getConfigKey(facet, KEY_SUFFIX_SORT_ORDER_LAST));
if (first.isEmpty() && last.isEmpty()) {
return;
}
log.debug("Run plugin by enabling custom sorting for '{}' facet", facet.getName());
facet.setCustomComparator(new FacetsCustomSortPositionComparator(first, last));
});
}
}
The class below implements the custom sorting logic within a Java comparator. This comparator promotes or demotes specific category values resulting in a custom sort order for the faceted navigation categories.
FacetsCustomSortPositionComparator.java
package com.funnelback.plugin.facetsCustomSortPosition;
import com.funnelback.publicui.search.model.transaction.Facet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
public class FacetsCustomSortPositionComparator implements Comparator<Facet.CategoryValue> {
private static final Logger log = LogManager.getLogger(FacetsCustomSortPositionComparator.class);
private HashMap<String, String> order = new HashMap<String, String>();
/**
*
* @param firstPosition list of category labels to be ordered at the beginning
* @param lastPosition list of category labels to be ordered at the end
*/
FacetsCustomSortPositionComparator(List<String> firstPosition, List<String> lastPosition) {
final String o = "\u0000\u0000\u0000"; // smallest code point, equivalent of NUL char
final String f = "\uFFFE\uFFFE\uFFFE"; // one of the last code points, non-character
setOrder(firstPosition, o);
setOrder(lastPosition, f);
log.debug("Facet categories will by sorted by provided custom order {}", order);
}
@Override
public int compare(Facet.CategoryValue cv1, Facet.CategoryValue cv2) {
String l1 = getOrderKey(cv1.getLabel());
String l2 = getOrderKey(cv2.getLabel());
Boolean toCompare = false;
if (order.containsKey(l1)) {
l1 = order.get(l1);
toCompare = true;
}
if (order.containsKey(l2)) {
l2 = order.get(l2);
toCompare = true;
}
return toCompare ? l1.compareTo(l2) : 0;
}
/**
* Get lower-cased category label
* @param label
*/
private String getOrderKey(String label) {
return label.toLowerCase(Locale.ROOT);
}
/**
* Create order key use to sort category labels
* @param orderPrefix string prefix used to ensure value will be marked as one of the first or last
* @param orderPosition for multiple category labels maintain the order from configuration
*/
private String getOrderVal(String orderPrefix, int orderPosition) {
return orderPrefix + String.format("%06d", orderPosition);
}
/**
* Create order list of category labels
* @param categories list of category labels to be ordered
* @param orderPrefix string prefix used to ensure value will be marked as one of the first or last
*/
private void setOrder(List<String>categories, String orderPrefix) {
for (int i = 0; i < categories.size(); i++) {
order.put(getOrderKey(categories.get(i)), getOrderVal(orderPrefix, i));
}
}
}