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. Optimizely offers detailed instructions on customizing 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
After configuring the default provider in the admin section, the site will use Elasticsearch v7, conducting searches on indexes newly established 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. Handle data retrieval within custom code by composing a LINQ query to fetch the required 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
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
Very Informative Blog!! Keep Writing..