The Optimizely configured commerce introduces Elasticsearch v7 for a better search experience. In the dynamic landscape of the Commerce world, there is always room for extended code customization. Furthermore, Optimizely provides explicit steps to customize Elasticsearch v7 indexes.

There are a lot of advantages to using Elasticsearch v7. some are

Improved Performance
Security Enhancements
Elasticsearch SQL
GeoJSON Support
Usability and Developer-Friendly Features

In this post, we will go through how we will add the custom column in the Elasticsearch v7 index step by step.

Setting Elasticsearch v7 as a Default Provider from the Admin

The very first step is to set a default provider in admin. Below are the steps to set the default provider:

Login into Admin
Navigate to the Settings
Search for “Search Provider Name”
Set Elasticsearch v7 into “Search Provider Name” and “Search Indexer Name” (See Screenshot)
Click on Save.

 

Creating Custom Field

Once the admin section sets the default provider, the site will point to Elasticsearch v7 and perform searches on the indexes newly created by Elasticsearch v7.

If we want to add a new custom field to these indexes, Optimizely provides some pipelines to add the new custom field.

Add a Class into the Solution to Extend the ElasticsearchProduct Class and Create a New Field

In this class, we have created a property named StockedInWharehouses which is the type of list of strings.

namespace Extensions.Search.ElasticsearchV7.DocumentTypes.Product
{
using Insite.Search.ElasticsearchV7.DocumentTypes.Product;
using Nest7;

[ElasticsearchType(RelationName = “product”)]
public class ElasticsearchProductCustom : ElasticsearchProduct
{
public ElasticsearchProductCustom(ElasticsearchProduct source)
: base(source) // This constructor copies all base code properties.
{
}

[Keyword(Index = true, Name = “stockedInWarehouses”)]
public List<string> StockedInWarehouses { get; set; }
}
}

Override Pipeline to Insert Data into Custom Property

To add the data into custom property, use a PrepareToRetrieveIndexableProducts class extension. Here, take care of fetching data into custom code, i.e., write a LINQ query to retrieve data. The best performance achive by returing Dictonary like.ToDictionary(record => record.ProductId). Here is an example code snippet

namespace Extensions.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Pipes.PrepareToRetrieveIndexableProducts
{
using Insite.Core.Interfaces.Data;
using Insite.Core.Plugins.Pipelines;
using Insite.Data.Entities;
using Insite.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Parameters;
using Insite.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Results;
using System.Linq;

public sealed class PrepareToRetrieveIndexableProducts : IPipe<PrepareToRetrieveIndexableProductsParameter, PrepareToRetrieveIndexableProductsResult>
{
public int Order => 0; // This pipeline has no base code so Order can be anything.

public PrepareToRetrieveIndexableProductsResult Execute(IUnitOfWork unitOfWork, PrepareToRetrieveIndexableProductsParameter parameter, PrepareToRetrieveIndexableProductsResult result)
{
result.RetrieveIndexableProductsPreparation = unitOfWork.GetRepository<ProductWarehouse>().GetTableAsNoTracking()
.Join(unitOfWork.GetRepository<Product>().GetTableAsNoTracking(), x => x.ProductId, y => y.Id, (x, y) => new { prodWarehouse = x })
.Join(unitOfWork.GetRepository<Warehouse>().GetTableAsNoTracking(), x => x.prodWarehouse.WarehouseId, y => y.Id, (x, y) => new { Name = y.Name, productId = x.prodWarehouse.ProductId })
.GroupBy(z => z.productId).ToList()
.Select(p => new {
productId = p.Key.ToString(),
warehouses = string.Join(“,”, p.Select(i => i.Name))
})
.ToDictionary(z => z.productId, x => x.warehouses);

return result;
}
}
}

Assign Data to Custom Property in the Elasticsearch7

__PRESENT

After retrieving data into the “RetrieveIndexableProductsPreparation” result property, set the data into a custom property for indexable products. To achieve this crate a class “ExtendElasticsearchProduct” and extend with IPipe<CreateElasticsearchProductParameter, CreateElasticsearchProductResult>

Here in the execute method, the parameter contains the RetrieveIndexableProductsPreparation property and this contains our data. Fetch this data using the TryGetValue method.

Avoid having the logic to fetch data return in the CreateElasticsearchProductResult extension class. Writing the data retrieval logic in this class will impact the performance of creating the product indexes.

Here you’ll find an illustrative code snippet:

namespace Extensions.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Pipes.CreateElasticsearchProduct
{
using System;
using System.Collections.Generic;
using Insite.Core.Interfaces.Data;
using Insite.Core.Plugins.Pipelines;
using Insite.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Parameters;
using Insite.Search.ElasticsearchV7.DocumentTypes.Product.Index.Pipelines.Results;

public sealed class ExtendElasticsearchProduct : IPipe<CreateElasticsearchProductParameter, CreateElasticsearchProductResult>
{
public int Order => 150;

public CreateElasticsearchProductResult Execute(IUnitOfWork unitOfWork, CreateElasticsearchProductParameter parameter, CreateElasticsearchProductResult result)
{
var elasticsearchProductCustom = new ElasticsearchProductCustom(result.ElasticsearchProduct);

if (((Dictionary<Guid, int>)parameter.RetrieveIndexableProductsPreparation).TryGetValue(elasticsearchProductCustom.ProductId.ToString(), out var stockedInWarehouses))
{
elasticsearchProductCustom.StockedInWarehouses = ExtractList(stockedInWarehouses);
}

result.ElasticsearchProduct = elasticsearchProductCustom;

return result;
}

private static List<string> ExtractList(string content)
{
if (string.IsNullOrWhiteSpace(content))
return new List<string>();
return content
.Split(new[] { ‘,’ }, StringSplitOptions.RemoveEmptyEntries)
.ToList();
}
}
}

After rebuilding the full product index, it displays the newly created ‘StockedInWarehouses’ column in product indexes. The below screeshot showing index with value

Term Query to filter StockedInWarehouses

Now you can easily use the StockedInWarehouses field in term query to filter out search results.

var currentWarehouseId = SiteContext.Current.PickUpWarehouseDto == null
? SiteContext.Current.WarehouseDto.Name
: SiteContext.Current.PickUpWarehouseDto.Name;

result.StockedItemsOnlyQuery = result
.SearchQueryBuilder
.MakeTermQuery(“stockedInWarehouses.keyword”, currentWarehouseId);

Reference Link : https://docs.developers.optimizely.com/configured-commerce/docs/customize-the-search-rebuild-process

__PRESENT__PRESENT__PRESENT__PRESENT

__PRESENT

__PRESENT

__PRESENT

__PRESENT

__PRESENT

__PRESENT

__PRESENT

__PRESENT

__PRESENT

__PRESENT__PRESENT