Coveo’s Excerpt Field
The excerpt field in Coveo is a segmented text generated at query time from the body of an item in a Coveo organization. When a query is performed, the excerpt yields relevant item body sections in which the queried terms are highlighted. From the Coveo glossary. For more information about how to control it, read this blog from one of Fishtank’s best.
React Implementation
Since the field is generated automatically, it’s as simple as adding field=”excerpt”
to an Atomic element. Here’s a small snippet to get you started. Not all imports are listed.
import {
...
AtomicSearchInterface,
AtomicResultSectionExcerpt,
AtomicResultText,
Result,
buildSearchEngine,
...
} from '@coveo/atomic-react';
...
<AtomicSearchInterface>
...
<AtomicResultListdisplay="grid"
template={(result)=> (
<>
<AtomicResultText field="title"></AtomicResultText>
<AtomicResultSectionExcerpt >
<AtomicResultText field="excerpt" />
</AtomicResultSectionExcerpt>
</>
)}
/>
...
</AtomicSearchInterface>
...
I’ve styled it a little, but you get results like this:
It will highlight the word you searched for (eg. with)
Duplicate Title in Excerpt Problem
One issue with the automatic excerpts is that the title could be duplicated in the excerpt like these images below.
Let’s fix this by editing the excerpt.
The Solution
First let’s take a look at the Results import (result.d.ts). If you scroll down, you will notice two fields: title
and excerpt
.
/**
* Contains the title of the item.
*/
title: string;
/**
* The contextual excerpt generated for the item (see the excerptLength query parameter).
*
* @example
* ... the new Coveo Cloud V2 and Coveo Cloud V1 ... the main differences between the two Coveo Cloud versions ...
*/
excerpt: string;
We can get these from the search results, and process them there. Add preprocessSearchResponseMiddleware
to your search configuration options when building the search engine as shown in the code below. Read the comments for more information, and read the related Coveo documentation here.
let engine = buildSearchEngine({
configuration: {
accessToken: accessToken,
organizationEndpoints: getOrganizationEndpoints(organizationName),
organizationId: organizationName,
search: {
...
preprocessSearchResponseMiddleware: (response) => {
// loop
response.body.results.forEach((result: Result) => {
// E.g., modify the result object
...
// getting title
let titleString = result?.title as string;
// getting excerpt
let excerptString = result?.excerpt as string;
// if the excerpt starts with the title, remove the titleString from the excerptString
if(richTextExcerpt.startsWith(titleString)) {
richTextExcerpt = richTextExcerpt.slice(titleString.length).trim();
}
// this will add an extra field into your result called modifiedExcerpt
result.raw.modifiedExcerpt = richTextExcerpt;
return result;
});
return response;
},
},
},
}
Now that you’ve modified your excerpt, you can display it by changing field=”excerpt”
to use the new modifiedExcerpt
field like.
<AtomicSearchInterface>
...
<AtomicResultListdisplay="grid"
template={(result)=> (
<>
<AtomicResultText field="title"></AtomicResultText>
<AtomicResultSectionExcerpt >
<AtomicResultText field="modifiedExcerpt" />
</AtomicResultSectionExcerpt>
</>
)}
/>
...
</AtomicSearchInterface>
After reloading your site, you should now see the new search results! You can do more to remove the starting ellipses if you wish.
Search Term Highlighting Issue
Unfortunately, the modified excerpt no longer has the search term highlighted. In this picture, I searched for ‘with’:
As you can see, the title is still removed from the excerpt, but with is not longer in bold. Here’s how we can fix that.
Adding Highlighting to Search Result Excerpt
Let’s take a look at results.d.ts again. There is a field named excerptHighlights
which contains an array of HighlightKeyword
objects
/**
* The length and offset of each word to highlight in the item excerpt string.
*/
excerptHighlights: HighlightKeyword[];
export interface HighlightKeyword {
/**
* The 0 based offset inside the string where the highlight should start.
*/
offset: number;
/**
* The length of the offset.
*/
length: number;
}
For example if we had the following excerpt
"Working with dedication, she managed to complete the project with ease…"
excerptHighlights would look like this: {[8,4],[53,4]}
With that in mind, I’ve created a helper function to add in bold tags at those offsets. Take a look at the code below; the comments will explain more
/**
* Adds highlights (tags of user's choice eg. <strong>) to a string of text based on offset and length
* @param text - Text to be changed.
* @param highlights - array of Highlight objects (length: number, offset: number)
* @param startTag - starting tag eg. <b>
* @param endTag - closing tag eg. </b>
* @returns The CSS class for the image position.
*/
export function addHighlights(text: string, highlights: Highlight[], startTag: string, endTag: string): string{
// reverse the order of the highlight array. This is so we add tags to the end of the string first.
// If we add tags to the front first, the string indexes will be changed for the next set of tags
highlights.sort((a, b) => b.offset - a.offset);
highlights.forEach(({ length, offset }) => {
// add tag to the end of the offset
text = text.slice(0, offset + length) + endTag + text.slice(offset + length);
// add tag to the start of the offset
text = text.slice(0, offset) + startTag + text.slice(offset);
});
return text;
}
/**
* Adds <b> tags to a string of text based on offset and length
* @param text - Text to be changed.
* @param highlights - array of Highlight objects (length: number, offset: number)
* @returns The CSS class for the image position.
*/
export function addBoldTags(text: string, highlights: Highlight[]): string {
return addHighlights(text, highlights, '<b>', '</b>');
}
In preprocessSearchResponseMiddleware
, modify the code to call the new function
// modify excerpt
let titleString = result?.title as string;
let excerptString = result?.excerpt as string;
let richTextExcerpt = addBoldTags(excerptString, result.excerptHighlights);
if(richTextExcerpt.startsWith(titleString)) {
richTextExcerpt = richTextExcerpt.slice(titleString.length).trim();
}
result.raw.modifiedExcerpt = richTextExcerpt;
Inside AtomicResultSectionExcerpt
I’ve set up a function to return html text, and then output the raw html string, which has tags.
...
function createMarkup(text) {
return {__html: text};
}
...
<AtomicResultSectionExcerpt class="descriptionSection">
<p dangerouslySetInnerHTML={createMarkup(result.raw.modifiedExcerpt)}/>
</AtomicResultSectionExcerpt>
...
And here’s the end result! As you can see, the search term is bolded, and the title is included in the excerpt, because the search term was in the title.
Final Thoughts on Mastering Coveo’s Excerpt Field
This blog walked you through the steps of editing Coveo’s automatically generated excerpt field. Thank you for reading!