Jeremy Davis
Jeremy Davis
Sitecore, C# and web development
Article printed from: https://blog.jermdavis.dev/posts/2015/coveo-facets-based-on-id-fields

Coveo facets based on ID fields

Published 01 September 2015
Updated 25 August 2016

A while back I had the chance to look into the my initial install issue, I've spent a bit of time looking at Coveo's approach to the faceted search I'd used on some recent projects. Whilst this mostly works really easily, I've spotted a minor shortcoming in the out-of-the-box functionality which needed a quick work around for my scenario to work.

[I'm working in Sitecore 7.2 here – but this should be applicable to more recent releases too]

The first thing to do when adding facets to your Coveo UI is that you have to tell Sitecore that the field in question should be a facet. This works the same way as for any other search framework integrated with Sitecore, by creating a new item under /sitecore/system/Settings/Buckets/Facets using the Facet template. They key fields to fill in here are Name, Display Name and Field Name:

Facet Definition

Name and Display Name are easy - you can choose whatever sensible values you want here. The Field Name must be the defined name for the field in the search index. This is generally the Sitecore field name from your data template, in lower case. Though if you're faceting on a computed field you need to use the name you defined in your configuration.

When you now build your index, the integration between Coveo and Sitecore will insure that this field is marked as a facet inside the Coveo index. One thing that confused me briefly, is that when you look at your index inside Coveo, all the fields have very different names:

Coveo Field Names

Ignore the "Name" field here. Internally, Coveo uses these unique names to make sure that if you index two things which appear to have the same name there won't be a clash inside their index. But when you're working inside Sitecore this is hidden from you, as it's using Coveo's "Metadata Name" field.

With the facet definition in place, the second thing you have to do is add one of Coveo's facet UI controls to your search page. With their default UI components, you insert the appropriate facet component into the left hand placeholder called "Coveo facets", and set its properties:

Insert Facet

The two properties to fill in here are the Title (which can be whatever text you want) and Field, where you pick from the set of facets defined inside Sitecore as above.

An interesting side note here is that the data you enter in this properties dialog is not setting fields in a data source for this component. It is setting attributes in the xml for the Layout Delta inside the __Renderings field. This is probably of most interest if you're working in Sitecore v8, where the layout and components on a page can be versioned. But it also explains where this data goes if you were confused by why the component has no data source.

When you close the properties dialog, you'll see the facet data appear on the page. But if (like me) the metadata you were faceting on is a look-up field that stores IDs, what you'll see isn't very helpful:

Facet Guids

Your users won't want to work with GUIDs. So the next step is working out how to get the facet UI to look up the target items to display the appropriate text. Initially, I went down a bit of a rabbit hole here, and wasted a load of time trying to work out how the properties of the facet control for lookups worked:

Facet Lookup Option

But it turns out (having done some training) that these aren't the droids settings you're looking for...

To get the correct lookup value displayed, you need to base your facet component's configuration on an index field which has the right data in it. And in most cases, the way to do this is with a computed field in your index. Coveo's framework helpfully provides the code for a generic lookup-based computed field in the Coveo.SearchProvider.ComputedFields.ReferencedFieldComputedField class in the Coveo.SearchProviderBase assembly. In the computed fields section of your index configuration, you can create a new field which uses this class to perform a lookup for you. For example:

<fields hint="raw:AddComputedIndexField">
    <!-- other computed field definitions -->
    <field fieldName="courseSubjectFacet" sourceField="CourseSubject" referencedFieldName="SubjectName">Coveo.SearchProvider.ComputedFields.ReferencedFieldComputedField, Coveo.SearchProviderBase</field> 
</fields>

					

Each time the item being processed contains the sourceField this class will use it's ID to load the target item and try to extract the referencedFieldName field from it, and supply that value for indexing. Once you've added the computed field definition to your Sitecore index configuration, you can adjust your facet definition item to use the computed index field rather than the original field.

This works great if the data you want to refer to is actually a field on the target item. But if you configure the referencedFieldName attribute with the value Name to fetch the target item's name then it's not so successful:

Name Facet

Now the facet has no values... A bit of digging shows this is because the computed field is empty in the underlying index, and hence there are no values to facet on.

It's a common pattern in Sitecore data templates to avoid having your own field for "name" if you can use the system properties for Display Name or Name instead. And since the DisplayName property falls back to Name if the Display Name is not defined, it makes sense to be able to uses these pseudo-fields (for want of a better term) when you're generating a facet.

It's a fairly simple fix, but it does require a bit of code. Not having the source for this class, I resorted to decompiling it via ILSpy for the purposes of this experiment. Based on that, I worked out you can adjust the code in the GetItemValue() method inside the ReferencedFieldComputedField class to check if the referencedFieldName refers to Name or Display name with something like:

private string GetItemValue(string p_Id, Language p_SourceItemLanguage)
{
    string result = null;
    IItem item = this.ResolveReferencedItem(p_Id, p_SourceItemLanguage);
    if (item != null)
    {
        switch(this.ReferencedFieldName.ToLowerInvariant())
        {
            case "name":
                result = item.Name;
                break;
            case "displayname":
                result = item.DisplayName;
                break;
            default:
                result = item.GetFieldValue(this.ReferencedFieldName);
                break;
        }
    }
    return result;
}

					

Rather than just calling item.GetFieldValue() (which won't return a value for things that aren't fields) the case statement allows the code to return either the Name or DisplayName property directly if appropriate. For production code it would be sensible to refactor a bit to get rid of the call to ToLowerInvariant() here for every call, as it could be pre-computed, but I've left all the changes in the one method for blog-example simplicity.

If you re-configure your computed facet field to use your own version of the class, then you can configure it to extract the Display Name into the computed field:

<fields hint="raw:AddComputedIndexField">
    <field fieldName="courseSubjectFacet" sourceField="CourseSubject" referencedFieldName="DisplayName">Testing.Coveo.ReferencedFieldComputedField, Testing.Coveo</field>
</fields>

					

And then you end up with the right result:

Working Facet

Alternatively, of course, you can always create your own lighter-weight computed field class(es) which perform only the exact lookup you require.

↑ Back to top