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;
}