Extend Orders with extra index fields in your Sitecore Commerce 10

Hello dear developers and happy Sitecorians, the summer is almost gone(here in Europe at least). Autumn is coming and with that the wonderful and lovely Sitecore Symposium. And guess what? It’s all free, hurry up and sign up to the best event ever (besides from Microsoft’s – .NET Conf 2021 ) – https://symposium.sitecore.com/

Today’s post will about Sitecore Commerce and how to add extra (solr index) fields to Orders.

Our scenario is this, we have added a new custom component to the orders. The component contains data from an external ERP system. Now the BizFx users want to be able to search for orders using the “extra” data. So how do we do this?

Thankfully we don’t have to re-invent the wheel, there are some great blog posts out there. Especially this lovely gem from Gaurav Agarwal, thank you for this 😊:
BizFx – Add new field in search criteria in Customers, Orders, Catalogs

Before we begin, I just want to give a huge shout out to Gaurav Agarwal again. I was really stuck here. I thought I could add the new index field in the PlugIn.Search.Solr.PolicySet-1.0.0.json, like you normally do. But no, not for Orders (in BizFx). So thank you again for this great blog post! And keep blogging!

Our solution will be similar(to the post above), except for some minor changes. Let’s get started and follow the post!

To “extend” Order with index fields, we need to do this in the Sitecore.Commerce.Engine (SDK) solution. And as the blog describes, we will have to hook up to the pipelines IFullIndexMinionPipeline, IIncrementalIndexMinionPipeline. In order to hook up to the pipelines we will need a block.

I just love this, so smart to split it up in pipelines and blocks. And of course, it’s all in Asp .Net Core – what a dream 😘 Start working with Sitecore Commerce, developers! You will love it. Sitecore has also great documentation(as always) about Sitecore Commerce and handling pipelines and blocks, check it out people => Working with Commerce Engine pipelines and blocks

Sorry for the interruption, I got a bit excited here 😁. So we will create a block, which will hook up to the pipelines IFullIndexMinionPipeline, IIncrementalIndexMinionPipeline. Let’s create the block and call it ExtendOrderIndexingViewBlock. The block will grab the data from the custom component and put it into ViewProperties

using Sandbox.Commerce.Plugin.ERP.Components;
using Sitecore.Commerce.Core;
using Sitecore.Commerce.EntityViews;
using Sitecore.Commerce.Plugin.Orders;
using Sitecore.Commerce.Plugin.Search;
using Sitecore.Framework.Conditions;
using Sitecore.Framework.Pipelines;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Sandbox.Commerce.Plugin.Sandbox.Pipelines.Blocks.ExtendCommerceIndexes
{
    [PipelineDisplayName("Orders.block.ExtendOrderIndexingViewBlock")]
    public class ExtendOrderIndexingViewBlock : AsyncPipelineBlock<EntityView, EntityView, CommercePipelineExecutionContext>
    {
        
        /// <summary>
        /// Runs the specified argument.
        /// </summary>
        /// <param name="arg">The argument.</param>
        /// <param name="context">The context.</param>
        /// <returns>An <see cref="EntityView"/></returns>
        
        public override Task<EntityView> RunAsync(EntityView arg,
      CommercePipelineExecutionContext context)
        {
            Condition.Requires(arg).IsNotNull($"{this.Name}: argument cannot be null.");
            var argument = context.CommerceContext.GetObjects<SearchIndexMinionArgument>().FirstOrDefault();
            if (string.IsNullOrEmpty(argument?.Policy?.Name))
            {
                return Task.FromResult(arg);
            }

            var orders = argument.Entities?.OfType<Order>().ToList();
            if (orders == null || !orders.Any())
            {
                return Task.FromResult(arg);
            }

            var searchViewNames = context.GetPolicy<KnownSearchViewsPolicy>();
            var knownOrderStatusPolicy = context.GetPolicy<KnownOrderStatusPolicy>();

            orders.ForEach(
                order =>
                {
                    var documentView = arg.ChildViews.Cast<EntityView>()
                        .FirstOrDefault(v => v.EntityId.Equals(order.Id, StringComparison.OrdinalIgnoreCase) && v.Name.Equals(searchViewNames.Document, StringComparison.OrdinalIgnoreCase));
                    if (documentView == null)
                    {
                        documentView = new EntityView
                        {
                            Name = context.GetPolicy<KnownSearchViewsPolicy>().Document,
                            EntityId = order.Id
                        };
                        arg.ChildViews.Add(documentView);
                    }

                 
                    var erpOrderNumber = order.GetComponent<MyCustomErpComponent>()?.ErpOrderNumber;
                    if (!string.IsNullOrWhiteSpace(erpOrderNumber))
                        documentView.Properties.Add(new ViewProperty { Name = "ErpOrderId", RawValue = erpOrderNumber });


                });
            return Task.FromResult(arg);
        }

        public ExtendOrderIndexingViewBlock()
      : base((string)null)
        {
        }
    }
}

Next will be to hook up the block to the pipelines, this is done in the class ConfigureSitecore. Here we will do a minor change, instead of replacing the InitializeOrdersIndexingViewBlock. We will add the new block after the InitializeOrdersIndexingViewBlock.

.ConfigurePipeline<IFullIndexMinionPipeline>(config => config
    .Add<ExtendOrderIndexingViewBlock>().After<InitializeOrdersIndexingViewBlock>()
)

.ConfigurePipeline<IIncrementalIndexMinionPipeline>(config => config
    .Add<ExtendOrderIndexingViewBlock>().After<InitializeOrdersIndexingViewBlock>()
)

Let’s follow the blog… One more thing to do in the Sitecore Commerce solution is to update the PlugIn.Search.PolicySet-1.0.0.json file. Locate “SearchScopeName”: “PlaceholderForSearchIndexPrefixOrdersScope” and add your new field.

{
	"$type": "Sitecore.Commerce.Plugin.Search.IndexablePolicy, Sitecore.Commerce.Plugin.Search",
	"SearchScopeName": "PlaceholderForSearchIndexPrefixOrdersScope",
	"Properties": {
	  
	  **Here is a bunch of fields**
	  
	  "ErpOrderId": {
		"TypeName": "System.String",
		"IsKey": false,
		"IsSearchable": true,
		"IsFilterable": true,
		"IsSortable": true,
		"IsFacetable": false,
		"IsRetrievable": true
	  }
	}
}

The PlaceholderForSearchIndexPrefixOrdersScope is a “placeholder” which will be set in the docker-compose.yml:
COMMERCEENGINE_SearchIndexPrefixOrdersScope: OrdersScope
And in the k8 spec’s:
– name: COMMERCEENGINE_SearchIndexPrefixOrdersScope          
value: OrdersScope

Great, next will be to build the (engine) image/s and hit docker-compose up. You are running your stuff in Docker, right? If not, then you should really consider it. And if you have bad excuses, like it’s too hard or it’s too complicated. You are wrong! It’s very easy and (as always) Sitecore has great documentation which will guide you in your Docker journey:
https://doc.sitecore.com/en/developers/100/developer-tools/containers-in-sitecore-development.html
https://doc.sitecore.com/en/developers/101/developer-tools/building-custom-sitecore-images.html
https://doc.sitecore.com/en/developers/101/sitecore-experience-commerce/sitecore-commerce-container-sdk.html

We are almost finished with the Sitecore Commerce stuff. We need to do a “Bootstrap” since we have done changes in a “configuration” file – PlugIn.Search.PolicySet-1.0.0.json. Let’s use Postman for the “Bootstrap” and ensure that the changes are propagated to the global database.

The last part is to update the solr managed-schema for the OrdersScope. We need to add the new field ErpOrderId. So how do we do this? We could do this manually, locate the managed-schema file and add the field. Here it is in the container:

Or use the Solr Schema API. This is a more elegant solution 😘. And guess what, Sitecore has it documented in Configure the Solr search provider for xconnect. So that got me thinking, why not do the same for OrdersScope. Try it yourself (if you are running commerce), here is how to get the managed-schema for OrdersScope:
http://localhost:8984/solr/OrdersScope/schema

Ok, let’s use the API to update the managed-schema. We will make post to the API, the post will contain the new field. Here is the JSON which we will post. Let’s put it in a JSON file (and name it SolrFieldsForOrdersScope.json):

{
  "add-field": [
    {
      "name": "erporderid",
      "type": "string",
      "indexed": true,
      "stored": true
    }
  ],
  "add-copy-field": [
    {
      "source": "erporderid",
      "dest": "_text_"
    }
  ]
}

Why not make it easier and put the “posting” in a PowerShell file (UpdateSolrCoresSchema.ps1):

$ErrorActionPreference = "Stop";

$solrUrl = "localhost:8984"
########################
# Update OrdersScope schema 
########################
$core = "OrdersScope"

$pathToSchemaFile="SolrFieldsForOrdersScope.json"
$apiUrl="http://$solrUrl/api/cores/$core/schema"
$requestBody = Get-Content $pathToSchemaFile -Raw 
Write-Host "Executing Solr commands:" $requestBody 
Invoke-WebRequest -Uri $apiUrl -ContentType "application/json" -Method Post -Body $requestBody


########################
# Update OrdersScope-Rebuild schema
########################
$core = "OrdersScope-Rebuild"

$pathToSchemaFile="SolrFieldsForOrdersScope.json"
$apiUrl="http://$solrUrl/api/cores/$core/schema"
$requestBody = Get-Content $pathToSchemaFile -Raw 
Write-Host "Executing Solr commands:" $requestBody 
Invoke-WebRequest -Uri $apiUrl -ContentType "application/json" -Method Post -Body $requestBody



And then we just call it from the command window or in your CI/CD pipeline:

.\UpdateSolrCoresSchema.ps1

I really like this API approach, it will work in your local dev environment but also on test, stage, or even production. And the API also supports collections(for the solr cloud people) 😁

That’s all for now folks 😊




Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.