Search Predictions and Auto Complete with SXA

SXA’s search components are pretty amazing, right out of the box! You can build up a functional search page, with results, paging, filters, etc… in a matter of minutes. All that’s really left is styling all the components with some CSS.

One of the features I’d like to dive deeper into is the Search Box component. More specifically, the Suggestions Mode of the Search Box component.

There is already some good documentation on these components on the Sitecore Docs site:

…just to list a couple…

Each of these pages even talk about the Suggestions mode (which is set to Show search results by default) and the differences between the 3 options. The second link even highlights that, in order to use Show predictions, you must use “…a suggester called sxaSuggester.”

Huh? What is a “suggester”? Is this something that’s already configured? It doesn’t sound like it… How do you configure a “suggester”?

Well, that’s what we’re going to cover in this post. How exactly to add and configure a search suggester component in Solr for use in the SXA Search Box component.

What is a “Suggester”?

You are welcome to dive deeper into the full documentation around the Suggester search component in Solr, but in short, the suggester is a “search component” that you can add to your solr configuration that will return suggestions for search terms, based on a configured field, for the term sent to the api. So, if you sent “artif” to the suggester, it might return results like “Artificial”, “Artificial Intelligence”, etc… It’s pretty cool that solr has this feature already and that you don’t have to build it yourself. You just need to configure it.

Configuring a Suggester

There are multiple posts, Tamer’s post for example, that already talk about how to add a suggester. There is even some Sitecore documentation around it, although it’s more general and not specific to SXA. But all of these examples talk about modifying the solrconfig.xml file in your index directly. What if you’re using SolrCloud? When running 10.1+ in local containers, solr is configured to run in SolrCloud mode, by default.

And if you’re using SolrCloud, the index configurations are stored in Zookeeper and are not editable directly. You must modify it through Solr’s API.

Ultimately, what we’re trying to achieve is to insert this configuration into our sitecore_sxa_master_index and sitecore_sxa_web_index:

<searchComponent name="suggest" class="solr.SuggestComponent">
  <lst name="suggester">
    <str name="name">sxaSuggester</str>
    <str name="lookupImpl"> AnalyzingInfixLookupFactory</str>
    <str name="field">metakeywords_t</str>
    <str name="suggestAnalyzerFieldType">text_general</str>
    <str name="buildOnStartup">true</str>
  </lst>
</searchComponent>

<requestHandler name="/suggest" class="solr.SearchHandler" startup="lazy">
  <lst name="defaults">
	<str name="suggest.dictionary">sxaSuggester</str>
    <str name="suggest">true</str>
    <str name="suggest.count">10</str>
  </lst>
  <arr name="components">
    <str>suggest</str>
  </arr>
</requestHandler>

You can do this by using the add-searchcomponent and add-requesthandler commands in the solr config api. How about a little PowerShell?

$solrConnectionString = "http://localhost:8983/solr"

function Add-SearchComponent {
    param(
        [string] $index
    )
    $url = ("{0}/{1}/config" -f $solrConnectionString, $index)

    $addComponentBody = @{
        "add-searchcomponent"= @{
            "name"="suggest"
            "class"="solr.SuggestComponent"
            "suggester"= @{
                "name"="sxaSuggester"
                "lookupImpl"="AnalyzingInfixLookupFactory"
                "field"="metakeywords_t"
                "suggestAnalyzerFieldType"="text_general"
                "buildOnStartup"="true"
            }
        }
    } | ConvertTo-Json
    Invoke-RestMethod -uri $url -Method POST -ContentType "application/json" -Body $addComponentBody
}
function Add-RequestHandler {
    param(
        [string] $index
    )
    $url = ("{0}/{1}/config" -f $solrConnectionString, $index)
    
    $addHandlerBody = @{
        "add-requesthandler"=@{
            "name"="/suggest"
            "class"="solr.SearchHandler"
            "startup"="lazy"
            "defaults"=@{
                "suggest"="true"
                "suggest.count"="10"
                "suggest.dictionary"="sxaSuggester"
            }
            "components"=@("suggest")
        }
    } | ConvertTo-Json
    Invoke-RestMethod -uri $url -Method POST -ContentType "application/json" -Body $addHandlerBody
}

Add-SearchComponent -index "sitecore_sxa_master_index"
Add-RequestHandler -index "sitecore_sxa_master_index"

That’s all nice and good, but what if I delete my solr indexes locally? Must I run this script manually each time?

We can do this through a custom entrypoint script with the solr-init container. I’ll show you.

Configuring solr-init to modify the solr configuration

First, let’s formalize the powershell above a little more, so it can be used as a custom entrypoint for solr-init. How about this:

function Test-ConfiguredSuggester {
    param(
        [string] $index
    )

    $suggesterUrl = ("{0}/{1}/suggest" -f $env:SITECORE_SOLR_CONNECTION_STRING, $index)
    $statusCode = 0
    try{
        $response = Invoke-WebRequest -Uri $suggesterUrl -UseBasicParsing
        $statusCode = $response.StatusCode
    } catch{
        $statusCode = $_.Exception.Response.StatusCode.Value__
    }
    return $statusCode -eq 200
}

function Add-SearchComponent {
    param(
        [string] $index
    )
    $url = ("{0}/{1}/config" -f $env:SITECORE_SOLR_CONNECTION_STRING, $index)

    $addComponentBody = @{
        "add-searchcomponent"= @{
            "name"="suggest"
            "class"="solr.SuggestComponent"
            "suggester"= @{
                "name"="sxaSuggester"
                "lookupImpl"="AnalyzingInfixLookupFactory"
                "field"="keywordtags_sm"
                "suggestAnalyzerFieldType"="text_general"
                "buildOnStartup"="true"
            }
        }
    } | ConvertTo-Json
    Invoke-RestMethod -uri $url -Method POST -ContentType "application/json" -Body $addComponentBody
}
function Add-RequestHandler {
    param(
        [string] $index
    )
    $url = ("{0}/{1}/config" -f $env:SITECORE_SOLR_CONNECTION_STRING, $index)
    
    $addHandlerBody = @{
        "add-requesthandler"=@{
            "name"="/suggest"
            "class"="solr.SearchHandler"
            "startup"="lazy"
            "defaults"=@{
                "suggest"="true"
                "suggest.count"="10"
                "suggest.dictionary"="sxaSuggester"
            }
            "components"=@("suggest")
        }
    } | ConvertTo-Json
    Invoke-RestMethod -uri $url -Method POST -ContentType "application/json" -Body $addHandlerBody
}

function Enable-Suggester {
    param(
        [string] $index
    )

    if (Test-ConfiguredSuggester -index $index) {
        write-host "sxaSuggester is already configured for $index"
    } else {
        write-host "configuring sxaSuggester for $index"
        Add-SearchComponent -index $index
        Add-RequestHandler -index $index
    }
}

Enable-Suggester -index "sitecore_sxa_master_index"
Enable-Suggester -index "sitecore_sxa_web_index"

.\Start.ps1 `
    -SitecoreSolrConnectionString $env:SITECORE_SOLR_CONNECTION_STRING `
    -SolrCorePrefix $env:SOLR_CORE_PREFIX_NAME `
    -SolrSitecoreConfigsetSuffixName $env:SOLR_SITECORE_CONFIGSET_SUFFIX_NAME `
    -SolrReplicationFactor $env:SOLR_REPLICATION_FACTOR `
    -SolrNumberOfShards $env:SOLR_NUMBER_OF_SHARDS `
    -SolrMaxShardsPerNodes $env:SOLR_MAX_SHARDS_NUMBER_PER_NODES `
    -SolrXdbSchemaFile .\\data\\schema.json `
    -SolrCollectionsToDeploy $env:SOLR_COLLECTIONS_TO_DEPLOY

We’ll call this CustomStart.ps1. What this script does is check the index to see if /suggest is configured and returns a 200 status code. If it returns a 404, we know that we need to call the api to add the components. Once it completes its check and configuration, it simply calls the default Start.ps1 from the base solr-init container to proceed as normal.

Next, we need to update the compose build Dockerfile for solr-init:

# escape=`

ARG BASE_IMAGE
ARG SXA_IMAGE

FROM ${SXA_IMAGE} as sxa
FROM ${BASE_IMAGE}

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]

# Add SXA module
COPY --from=sxa C:\module\solr\cores-sxa.json C:\data\cores-sxa.json
COPY "scripts\CustomStart.ps1" c:\

ENTRYPOINT ["powershell", "C:/CustomStart.ps1"]

Last but not least, update the docker-compose.override.yml file to hit your new custom entrypoint script:

solr-init:
  entrypoint: powershell -Command "& C:\CustomStart.ps1"
  image: ${REGISTRY}${COMPOSE_PROJECT_NAME}-xp0-solr-init:${VERSION:-latest}
  build:
    context: ./build/solr-init
    args:
      BASE_IMAGE: ${SITECORE_DOCKER_REGISTRY}sitecore-xp0-solr-init:${SITECORE_VERSION}
      SXA_IMAGE: ${SITECORE_MODULE_REGISTRY}sxa-xp1-assets:${SXA_VERSION}

Now, with all of this configured, if you set your Suggestions mode to Show predictions, you’ll actually get results back!

Next up is to actually wire up the suggestions returned in the popup to actually do something, rather than just look pretty. By default, no click events appear to be wired up… But, that’s a topic for another post. 🙂

Happy Sitecore trails, my friend!

Leave a comment