Thursday, July 13, 2017

"Top Searches" functionality using Sitecore ReportingService

Sitecore "Internal Search" report provides information about top keywords that were used in your website searches. Why not use this report to show top searched phrases on the website? (Thank you Tony Wang for the idea)

First of all you have to track the search queries in DMS. There are plenty of blog posts describing how to do that, so I am not going to explain it. I'll simply provide the code that I used to do that:

        public virtual void TrackSiteSearch(Item pageEventItem, string query)
        {
            Assert.ArgumentNotNull(pageEventItem, nameof(pageEventItem));
            Assert.IsNotNull(pageEventItem, $"Cannot find page event: {pageEventItem}");
            if (this.IsActive)
            {
                var pageEventData = new PageEventData("Search", Constants.PageEvents.Search)
                {
                    ItemId = pageEventItem.ID.ToGuid(),
                    Data = query,
                    DataKey = query,
                    Text = query
                };
                var interaction = Tracker.Current.Session.Interaction;
                if (interaction != null)
                {
                    interaction.CurrentPage.Register(pageEventData);
                }
            }
        }

Where Costants.PageEvents.Search is defined in

    public struct Constants
    {
        public struct PageEvents
        {
            public static Guid Search => Guid.Parse("{0C179613-2073-41AB-992E-027D03D523BF}");
        }
    }

After we put the code in place to track the search queries, we can start implementing the "Top Searches" functionality. Before you do that, make sure that the tracked data is showing up in "Internal Search" report in  Experience Analytics.

Now to the report call implementation.

I have implemented two methods to retrieve results from the report. One calls the ReportingService passing ReportQuery object and encoding the response data to get the same results Experience Analytics report shows. The second one creates ReportQuery with parameters that are required to get the right results.

public IEnumerable GetTopSearchQueries()
{
 try
 {
  var reportingService = ApiContainer.Repositories.GetReportingService();
  var reportQuery = GetReportQuery();
  ReportResponse reportResponse = reportingService.RunQuery(reportQuery);
  var encoder = ApiContainer.GetReportResponseEncoder();
  var result = encoder.Encode(reportResponse);
  if (result != null && result.Data != null && result.Data.Localization != null 
&& result.Data.Localization.Fields != null && result.Data.Localization.Fields.Any())
  {
   var searchFields = result.Data.Localization.Fields.FirstOrDefault();
   if (searchFields != null)
   {
    return result.Data.Localization.Fields.FirstOrDefault().Translations.Select(r => r.Value).ToList();
   }
  }
 }
 catch (Exception ex)
 {
  Log.Error(ex.Message, ex, this);
  var obj = CreateObject("reporting/dataProvider");
 }
 return new List();
}

private ReportQuery GetReportQuery()
{
 ReportQuery reportQuery = new ReportQuery();
 reportQuery.Site = "SUM";
 reportQuery.Segments = new string[] { 
ID.Parse(Settings.GetSetting("Tracking.AllVisitsByLocalSearchKeyword", 
"{8A86098B-2A7A-42CD-8B62-EC5AF1BE4D42}")).ToShortID().ToString() };
 reportQuery.Keys = new string[] { "ALL" };
 reportQuery.Fields = null;
 reportQuery.Parameters.DateFrom = DateTime.Now.AddMonths(-6);
 reportQuery.Parameters.DateTo = DateTime.Now.AddDays(1);
 reportQuery.Parameters.TimeResolution = TimeResolution.Collapsed;
 reportQuery.Parameters.KeyTop = 8;
 reportQuery.Parameters.KeySkip = 0;
 reportQuery.Parameters.PadEmptyDates = true;
 reportQuery.Parameters.KeyOrderBy = new FieldSort() { Direction = SortDirection.Desc, Field = SortField.Count };
 reportQuery.Parameters.KeyFromParent = string.Empty;
 reportQuery.Parameters.KeyFromAncestor = string.Empty;

 // RequestType is internal property, but it is required, so settings it through reflection
 var propertyInfo = reportQuery.GetType().GetProperty("RequestType", 
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
 propertyInfo.SetValue(reportQuery, 
System.Convert.ChangeType(Enum.Parse(propertyInfo.PropertyType,"0"), 
propertyInfo.PropertyType), null);

 return reportQuery;
}

Debugging Sitecore assemblies using dotPeek

Recently I had to figure out the right parameter set that a Sitecore reporting Api required to produce results I needed, and the only way to determine that was to step through Sitecore assemblies.

Google search produces a very helpful post by Igal Tabachnik at
https://hmemcpy.com/2014/07/how-to-debug-anything-with-visual-studio-and-jetbrains-dotpeek-v1-2/

Following the steps and this post, plus selecting "Enable .NET Framework source stepping" in General tab under Debug (see blow) made it possible.

Attach to Process as you normally do and land on specified break point.