Tag : tasks

Custom Product Sync Button

Today’s adventure is about custom ribbon buttons, asynchronous commands, and progress dialogs.

One of my recent projects deals with a 3rd party integration with a PIM (Product Information Management) system and Sitecore. This is not a very heavy integration. The PIM is the system of authority when it comes to a product’s description, price, color options, sizes, etc. Sitecore obtains this information via a REST API provided by the PIM. This information is fetched and transformed into Sitecore items, which we then store in a bucketed “Products” folder.

While this information doesn’t change very frequently, it can change. The client needed to have some way to trigger an update. I wanted to keep everything in Sitecore, rather than have some sort of custom admin page, which was the initial suggestion. I broke it down into 2 goals:

  1. Create some sort of button in Sitecore
  2. Create some sort of feedback dialog so the user knew what was happening

 

I started off creating the custom ribbon. There are many posts out there about how to create ribbons. I’ll add my favorite to the end of this post.

Custom Ribbon

With the pretty part out of the way, I moved on to wiring that button in.

The first step is building out the “Are you sure?” prompt:

public class ProductSyncCommand : Command
{
    // Sitecore will call Execute. This is the entry point.
    public override void Execute(CommandContext context)
    {
        // Here we're creating the dialog prompt to confirm the user's actions
        Sitecore.Context.ClientPage.Start(this, nameof(DialogProcessor));
    }
	public void DialogProcessor(ClientPipelineArgs args)
	{
		if (!args.IsPostBack)
		{
			SheerResponse.YesNoCancel("Are you sure you want to sync products?", "500px", "200px", true);
			args.WaitForPostBack(true);
		}
		else
		{
		    // TODO
		}
	}
}

The result is a pretty prompt, that doesn’t actually do anything:

Are you sure?

Next step is to add in logic for syncing the products.

private void SyncProducts(object[] parameters)
{
        // Tell Sitecore the job is Running 
        Sitecore.Context.Job.Status.State = JobState.Running;
	// Instead of using security disabler, I setup a dedicated account specifically for this
	using (new UserSwitcher("sitecore\\ProductJobAccount", true))
	{
		var products = GetAllProducts(); // some method that gets all products from the PIM

                // performance boost on creating items
		using (new BulkUpdateContext())
		{
			// Iterate through each product
			foreach (var product in products)
			{
				ProcessProduct(product); // Some method to handle adding/updating/removing this product to/from Sitecore
			}
		}
		RefreshIndex(); // some method that refreshes the tree from the bucket that the products are stored in
		try
		{
			PublishItems(); // some method that publishes the bucket that products are stored in
		}
		catch (Exception ex)
		{
			Log.Error(ex.Message, ex, this);
		}
	}
        // Tell Sitecore the job has finished
        Sitecore.Context.Job.Status.State = JobState.Finished;
}

We’ll also replace //TODO from earlier with:

    ProgressBox.Execute("Sync Products", "Sync Products", SyncProducts);
    SheerResponse.Alert("Product synchronization has completed");

At this point, we’ve wired in all the logic to do the following:

  1. switch into the “sitecore\\ProductJobAccount” (Security reasons. See side note below)
  2. Get all products from the PIM
  3. Enter a BulkUpdateContext. BulkUpdateContext speeds up item creation
  4. Iterate through each product and perform whatever business logic is in ProcessProduct();
  5. Refresh the indexes for the bucket tree
  6. Publish the items to the front end

As a side note: instead of using SecurityDisabler(), I recommend creating an account whose sole purpose is to do a job like this. This is better from a security standpoint, as you can ensure the account only has permission to do the job. It also allows for better audit trails.

Currently, this would show a progress bar, but provide no further feedback. I want to take this a step further, and provide step-by-step feedback to the user. To go about doing this, you can add messages to the job status. These messages appear on the progress bar.

Here’s the finished product:

public class ProductSyncCommand : Command
{
	public override void Execute(CommandContext context)
	{
		Sitecore.Context.ClientPage.Start(this, nameof(DialogProcessor));
	}

	public void DialogProcessor(ClientPipelineArgs args)
	{
		if (!args.IsPostBack)
		{
			SheerResponse.YesNoCancel("Are you sure you want to sync products?", "500px", "200px", true);
			args.WaitForPostBack(true);
		}
		else
		{
			if (args.Result == "yes")
			{
				ProgressBox.Execute("Sync Products", "Sync Products", SyncProducts);
				SheerResponse.Alert("Product synchronization has completed");
			}
		}
	}

	private void SyncProducts(object[] parameters)
	{
		Job job = Sitecore.Context.Job;
		job.Status.State = JobState.Running;
		// Instead of using security disabler, I setup a dedicated account specifically for this
		using (new UserSwitcher("sitecore\\ProductJobAccount", true))
		{
			job.Status.Messages.Add("Beginning fetch");
			var products = GetAllProducts(); // some method that gets all products from the PIM
			job.Status.Messages.Add("Fetch complete");

			// performance boost on creating items
			using (new BulkUpdateContext())
			{
				job.Status.Messages.Add("Syncing Products...");
				foreach (var product in products)
				{
					ProcessProduct(product); // Some method to handle adding/updating/removing this product to/from Sitecore
					job.Status.Processed++;
					job.Status.Messages.Add("Processed item " + job.Status.Processed);
				}
			}
			job.Status.Messages.Add("Sync complete...");
			job.Status.Messages.Add("Rebuilding indexes...");
			RefreshIndex(); // some method that refreshes the tree from the bucket that the products are stored in
			job.Status.Messages.Add("Done rebuilding indexes...");

			try
			{
				job.Status.Messages.Add("Publishing items...");
				PublishItems(); // some method that publishes the bucket that products are stored in
				job.Status.Messages.Add("Publish complete...");
			}
			catch (Exception ex)
			{
				Log.Error(ex.Message, ex, this);
			}
		}
		job.Status.Messages.Add("Job complete!");
		job.Status.State = JobState.Finished;
	}
}

You’ll see I added several calls to job.Status.Messages.Add(). These messages display one after the other on the progress bar prompt. When it runs, it’ll look like this:

Progress Dialog

The finished product met the business requirements, and fulfilled the goals I had set for the feature as well. The client now has the ability to trigger a product sync at the click of a button.

Useful Resources:

 

Update:

Since the writing of this post, the BulkUpdateContext and RefreshIndexes has been removed from the solution. We were running into issues keeping the indexes in sync, and removing BulkUpdateContext seems to have made it a little slower, but more consistent