Saturday, 29 April 2017

Building Custom Analytics Report – Part5 – Experience Analytics custom Filter

In previous posts in this series we discussed the business needs for custom analytics report, I explained how to collect data, how to save them and how to aggregate the data into reporting databases and how you can display them as a SPEAK report, in this part I will show you how you can create new filter in other words how you can extend the current existing filters.




If you missed the first two posts you can check them from here:

  1. -          Building Custom Analytics Report – Part1 – Introduction
  2. -          Build Custom Analytics Report - Part 2 – Storing and Retrieving Data
  3. -          Building Custom Analytics Report – Part3 – SPEAK Report
  4. -          Building Custom Analytics Report – Part 4 – Experience Analytics New Menu.

What are the default experience analytics filters components?

We already know that the default experience analytics filter come with date and subsite filters, to be able to extend the experience analytics filters we need to identify the related files for each component,  
Mainly there are three main components, the filters container view and its logic, the filters logic and the filter view and logic, following are the default paths:

·         Filters Container and its logic

This view  name is “ExperienceAnalyticsFilterscshtml” and will be located in the following path:
\Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\ExperienceAnalyticsFilters.cshtml

Following is the content of this file, and as you can see it include the default rendering “filters”, 

@using Sitecore
@using Sitecore.ExperienceAnalytics.Client
@using Sitecore.ExperienceAnalytics.Core.Extensions
@using Sitecore.Mvc
@using Sitecore.Web.UI.Controls.Common.Renderings
@using Sitecore.Web.UI.Controls.Common.UserControls
@using Globals = Sitecore.ExperienceAnalytics.Client.Globals
@using UserControl = Sitecore.Web.UI.Controls.Common.UserControls.UserControl
@model Sitecore.Mvc.Presentation.RenderingModel
@{
var rendering = Html.Sitecore().Controls().GetUserControl(Model.Rendering);
rendering.Class = "sc-ExperienceAnalyticsFilters";
rendering.Requires.Script("client", "ExperienceAnalyticsFilters.js");
rendering.Attributes.Add("data-bind", "visible: isVisible");
rendering.HasNestedComponents = true;
}
@EmbedFilters(rendering)
@helper EmbedFilters(UserControl rendering)
{
var renderingId = rendering.ControlId;
var helper = new RenderingHelper(Html, renderingId);
var selectedSubsiteName = "{Binding " + renderingId + "SubsiteFilter.SelectedSubsiteName}";
var fromDate = DateUtil.ToServerTime(DateTime.UtcNow).AddMonths(-1).ToDateRangeFormat();
var toDate = DateUtil.ToServerTime(DateTime.UtcNow).ToDateRangeFormat();
var dateRangeText = string.Concat(fromDate, " - ", toDate);
helper.MakeBorder("ContentWrapper", renderingId + "BarWrapper", contentWrapper =>
{
helper.MakeToggleButton("DateRangeToggleButton", contentWrapper, dateRangeText, true);
helper.MakeToggleButton("FilterToggleButton", contentWrapper, selectedSubsiteName, true);
});
// Rendering
<div @rendering.HtmlAttributes>
@Html.Sitecore().Controls().Rendering(Html, Globals.Bcl.Renderings.Containers.Border, renderingId + "BarWrapper", "", new { })
@Html.Sitecore().Controls().Rendering(Html, Globals.Layouts.Renderings.DateRangeFilter, renderingId + "DateRangeFilter", "", new
{
IsVisible = false
})
@Html.Sitecore().Controls().Rendering(Html, Globals.Layouts.Renderings.SubsiteFilter, renderingId + "SubsiteFilter", "", new
{
IsVisible = false
})
</div>
}

The JS logic file name is “ExperienceAnalyticsFilters.js” and will be located in the following path:
\Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\ExperienceAnalyticsFilters.js

Following is the content of this file:
define(["sitecore"], function (Sitecore) {
var App = Backbone.Model.extend({
cookiePrefix: "scExperienceAnalytics_",
illegalCharacters: /[/\\&%#?]/i,
attributes: {
"dateRange": null,
"subsite": null
},
getSubsite: function() {
var subsite = "all",
subsiteFromUrl = this.getSubsiteFromUrl(),
sessionSubsite = this.getSessionValue("Subsite");
if (subsiteFromUrl) {
subsite = subsiteFromUrl;
} else if (sessionSubsite) {
subsite = sessionSubsite;
}
return subsite;
},
textTemplate: function(template, data) {
if (template && data) {
_.each(data, function (value, variableKey) {
var regexp = new RegExp("\{\{\\s*" + variableKey + "\\s*\}\}", "g");
template = template.replace(regexp, value);
});
}
return template;
},
resolveTemplatesVariables: function (templates, variables) {
_.each(templates, _.bind(function (template, key) {
templates[key] = this.textTemplate(template, variables);
}, this));
return templates;
},
setSubsite: function(value) {
value = value || "all";
this.setSessionValue("Subsite", value);
this.updateUrl({
subsite: value
});
this.set("subsite", value);
},
getSubsiteFromUrl: function() {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return params.subsite;
},
convertDateFormat: function(date) {
return $.datepicker.formatDate("dd-mm-yy", $.datepicker.parseDate("d M ''y", date));
},
reConvertDateFormat: function(date) {
return $.datepicker.formatDate("d M ''y", $.datepicker.parseDate("dd-mm-yy", date));
},
updateDateRangeInSession: function(from, to) {
this.setSessionValue("FromDate", this.convertDateFormat(from));
this.setSessionValue("ToDate", this.convertDateFormat(to));
},
updateDateRangeInUrl: function(from, to) {
var hashObject = {
dateFrom: this.convertDateFormat(from),
dateTo: this.convertDateFormat(to)
};
this.updateUrl(hashObject);
},
setDateRange: function (from, to, persist) {
if (persist) {
this.updateDateRangeInSession(from, to);
}
this.updateDateRangeInUrl(from, to);
this.set("dateRange", {
dateFrom: from,
dateTo: to
});
},
getDateRange: function() {
var dateRangeFromUrl = this.getDateRangeFromUrl(),
sessionFromDate = this.getSessionValue("FromDate"),
sessionToDate = this.getSessionValue("ToDate"),
dateObject = null;
if (dateRangeFromUrl.dateFrom && dateRangeFromUrl.dateTo) {
if (this.validateDate(dateRangeFromUrl.dateFrom) && this.validateDate(dateRangeFromUrl.dateTo)) {
dateObject = dateRangeFromUrl;
dateObject.dateFrom = this.reConvertDateFormat(dateObject.dateFrom);
dateObject.dateTo = this.reConvertDateFormat(dateObject.dateTo);
} else {
return undefined;
}
} else if (sessionFromDate && sessionToDate) {
dateObject = {
dateFrom: this.reConvertDateFormat(sessionFromDate),
dateTo: this.reConvertDateFormat(sessionToDate)
};
}
return dateObject;
},
validateDate: function(dateString) {
return dateString.match(/^\d{2}[\-]\d{2}[\-]\d{4}$/);
},
getDateRangeFromUrl: function() {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return {
dateFrom: params.dateFrom,
dateTo: params.dateTo
};
},
updateUrl: function (hashObject) {
var params = this.removeEmptyParams(window.location.hash.substring(1));
params = params.replace(new RegExp("^&"), "");
params = (params.length > 0) ? "?" + params : "";
params = Sitecore.Helpers.url.addQueryParameters(params, hashObject).replace("?", "");
if (window.history.replaceState) {
window.history.replaceState({ state: null }, null, window.location.pathname + window.location.search + "#" + params);
} else {
window.location.hash = params;
}
},
removeEmptyParams: function(url) {
var params = Sitecore.Helpers.url.getQueryParameters(url);
for (var p in params) {
if (params[p] === "")
url = url.replace(new RegExp("&?" + p + "="), "");
}
return url;
},
getCookie: function(name) {
var cookie = document.cookie,
cookieArray = cookie.split(";");
for (var i = 0; i < cookieArray.length; i++) {
var cookieItem = cookieArray[i].trim().split("=");
if (cookieItem[0] === name) {
return cookieItem[1];
}
}
return null;
},
getSessionValue: function (name) {
return this.getCookie(this.cookiePrefix + name);
},
// TODO Make method private and insure that it will be executed just once on the start of application
setGlobalAjaxSettings: function () {
var ajaxSettings = {},
langFromCookies = this.getCookie("shell#lang");
// Needed to improve translation mechanism on the server
if (langFromCookies) {
ajaxSettings.headers = { "Accept-Language": langFromCookies };
}
$.ajaxSetup(ajaxSettings);
},
setSessionValue: function(name, value) {
document.cookie = this.cookiePrefix + name + "=" + value + ";path=/;";
},
/**
* Return first invalid string which matches with validator
* @param {array|string} list - array of strings which should be validated
* @param {RegExp} validator - regexp to find invalid sub-strings or characters
* @return {string|null} returns first invalid sub-strings or character if found or null if invalid character is not found
*/
findInvalidString: function (list, validator) {
if (typeof list === 'string') {
list = [list];
}
var invalidString = _.find(list, function (str) {
return validator.test(str);
});
if (invalidString) {
return invalidString.match(validator)[0];
} else {
return null;
}
}
});
var app = new App;
app.setGlobalAjaxSettings();
return app;
});


·         All filters logic

In this component you will find all the JS code that handle the different type of operations, like selecting subsite, picking date, setting values of filters and clearing them.
The file name is “ExperienceAnalytics.js” and can be found in the following path:
Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\Shared\ExperienceAnalytics.js

Following is the content of this file:


define(["sitecore"], function (Sitecore) {
var App = Backbone.Model.extend({
cookiePrefix: "scExperienceAnalytics_",
illegalCharacters: /[/\\&%#?]/i,
attributes: {
"dateRange": null,
"subsite": null
},
getSubsite: function() {
var subsite = "all",
subsiteFromUrl = this.getSubsiteFromUrl(),
sessionSubsite = this.getSessionValue("Subsite");
if (subsiteFromUrl) {
subsite = subsiteFromUrl;
} else if (sessionSubsite) {
subsite = sessionSubsite;
}
return subsite;
},
textTemplate: function(template, data) {
if (template && data) {
_.each(data, function (value, variableKey) {
var regexp = new RegExp("\{\{\\s*" + variableKey + "\\s*\}\}", "g");
template = template.replace(regexp, value);
});
}
return template;
},
resolveTemplatesVariables: function (templates, variables) {
_.each(templates, _.bind(function (template, key) {
templates[key] = this.textTemplate(template, variables);
}, this));
return templates;
},
setSubsite: function(value) {
value = value || "all";
this.setSessionValue("Subsite", value);
this.updateUrl({
subsite: value
});
this.set("subsite", value);
},
getSubsiteFromUrl: function() {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return params.subsite;
},
convertDateFormat: function(date) {
return $.datepicker.formatDate("dd-mm-yy", $.datepicker.parseDate("d M ''y", date));
},
reConvertDateFormat: function(date) {
return $.datepicker.formatDate("d M ''y", $.datepicker.parseDate("dd-mm-yy", date));
},
updateDateRangeInSession: function(from, to) {
this.setSessionValue("FromDate", this.convertDateFormat(from));
this.setSessionValue("ToDate", this.convertDateFormat(to));
},
updateDateRangeInUrl: function(from, to) {
var hashObject = {
dateFrom: this.convertDateFormat(from),
dateTo: this.convertDateFormat(to)
};
this.updateUrl(hashObject);
},
setDateRange: function (from, to, persist) {
if (persist) {
this.updateDateRangeInSession(from, to);
}
this.updateDateRangeInUrl(from, to);
this.set("dateRange", {
dateFrom: from,
dateTo: to
});
},
getDateRange: function() {
var dateRangeFromUrl = this.getDateRangeFromUrl(),
sessionFromDate = this.getSessionValue("FromDate"),
sessionToDate = this.getSessionValue("ToDate"),
dateObject = null;
if (dateRangeFromUrl.dateFrom && dateRangeFromUrl.dateTo) {
if (this.validateDate(dateRangeFromUrl.dateFrom) && this.validateDate(dateRangeFromUrl.dateTo)) {
dateObject = dateRangeFromUrl;
dateObject.dateFrom = this.reConvertDateFormat(dateObject.dateFrom);
dateObject.dateTo = this.reConvertDateFormat(dateObject.dateTo);
} else {
return undefined;
}
} else if (sessionFromDate && sessionToDate) {
dateObject = {
dateFrom: this.reConvertDateFormat(sessionFromDate),
dateTo: this.reConvertDateFormat(sessionToDate)
};
}
return dateObject;
},
validateDate: function(dateString) {
return dateString.match(/^\d{2}[\-]\d{2}[\-]\d{4}$/);
},
getDateRangeFromUrl: function() {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return {
dateFrom: params.dateFrom,
dateTo: params.dateTo
};
},
updateUrl: function (hashObject) {
var params = this.removeEmptyParams(window.location.hash.substring(1));
params = params.replace(new RegExp("^&"), "");
params = (params.length > 0) ? "?" + params : "";
params = Sitecore.Helpers.url.addQueryParameters(params, hashObject).replace("?", "");
if (window.history.replaceState) {
window.history.replaceState({ state: null }, null, window.location.pathname + window.location.search + "#" + params);
} else {
window.location.hash = params;
}
},
removeEmptyParams: function(url) {
var params = Sitecore.Helpers.url.getQueryParameters(url);
for (var p in params) {
if (params[p] === "")
url = url.replace(new RegExp("&?" + p + "="), "");
}
return url;
},
getCookie: function(name) {
var cookie = document.cookie,
cookieArray = cookie.split(";");
for (var i = 0; i < cookieArray.length; i++) {
var cookieItem = cookieArray[i].trim().split("=");
if (cookieItem[0] === name) {
return cookieItem[1];
}
}
return null;
},
getSessionValue: function (name) {
return this.getCookie(this.cookiePrefix + name);
},
// TODO Make method private and insure that it will be executed just once on the start of application
setGlobalAjaxSettings: function () {
var ajaxSettings = {},
langFromCookies = this.getCookie("shell#lang");
// Needed to improve translation mechanism on the server
if (langFromCookies) {
ajaxSettings.headers = { "Accept-Language": langFromCookies };
}
$.ajaxSetup(ajaxSettings);
},
setSessionValue: function(name, value) {
document.cookie = this.cookiePrefix + name + "=" + value + ";path=/;";
},
/**
* Return first invalid string which matches with validator
* @param {array|string} list - array of strings which should be validated
* @param {RegExp} validator - regexp to find invalid sub-strings or characters
* @return {string|null} returns first invalid sub-strings or character if found or null if invalid character is not found
*/
findInvalidString: function (list, validator) {
if (typeof list === 'string') {
list = [list];
}
var invalidString = _.find(list, function (str) {
return validator.test(str);
});
if (invalidString) {
return invalidString.match(validator)[0];
} else {
return null;
}
}
});
var app = new App;
app.setGlobalAjaxSettings();
return app;
});

·         Filter view and logic

For each filter we will find a view and JS file that handle the different operation, as example if we want to take the subsite filter as example, following are files related:

View file name is “SubsiteFilter.cshtml” and can be found in the following path:

Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\SubsiteFilter.cshtml

Log JS file name is “SubsiteFilter.js” and can be found in the following path:
Website\sitecore\shell\client\Applications\ExperienceAnalytics\Common\Layouts\Renderings\SubsiteFilter.js

This filter is actually a rendering that can be found in sitecore as an item, this can be found in the core database in the following path:
/sitecore/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/SubsiteFilter

There you can find all the rendering used in experience analytics.

Did you figure it out yet? J 

I believe you should have a clear idea of what I will explain next to extend experience analytics to add a new filter.

Custom Experience Analytics filters extending Steps?
  • Create new rendering component for the new filter.
  • Create the following files as a copy from the original:
    •  ExperienceAnalyticsFiltersExtended.js
    • ExperienceAnalyticsFiltersExtended.cshtml
    • ExperienceAnalyticsExtended.js
  • Update the above to include your new rendering “filter”

Example?

I want to add a new dropdown filter called “Event Type”, see the updates in the files below, I highlighted most of the files that I updated:

Event type filter files:

Event Type view


@using Newtonsoft.Json
@using Sitecore.ExperienceAnalytics.Client
@using Sitecore.Mvc
@using Sitecore.Web.UI.Controls.Common.Renderings
@using Sitecore.Web.UI.Controls.Common.Texts
@using Sitecore.Web.UI.Controls.Common.UserControls
@using UserControl = Sitecore.Web.UI.Controls.Common.UserControls.UserControl
@model Sitecore.Mvc.Presentation.RenderingModel
@{
var rendering = Html.Sitecore().Controls().GetUserControl(Model.Rendering);
rendering.Class = "sc-EventTypeFilter";
rendering.Requires.Script("client", "EventTypeFilter.js");
rendering.Attributes.Add("data-bind", "visible: isVisible");
rendering.HasNestedComponents = true;
var errorMessages = new
{
InvalidSubsite = "Error message"
};
rendering.Attributes.Add("data-sc-errortexts", JsonConvert.SerializeObject(errorMessages));
}
@EmbedEventTypeFilter(rendering)
@helper EmbedEventTypeFilter(UserControl rendering)
{
var renderingId = rendering.ControlId;
var helper = new RenderingHelper(Html, renderingId);
List<ComboBoxItem> options = new List<ComboBoxItem>();
try
{
ComboBoxItem oAll = new ComboBoxItem()
{
DisplayName = "All Types",
ItemId = "all"
};
ComboBoxItem oSingle = new ComboBoxItem()
{
DisplayName = "Single",
ItemId = "single"
};
ComboBoxItem oRecurring = new ComboBoxItem()
{
DisplayName = "Recurring",
ItemId = "recurring"
};
options.Add(oAll);
options.Add(oSingle);
options.Add(oRecurring);
}
catch (Exception exception)
{
ClientContainer.GetLogger().Error("An error occured while rendering sites filter. Exception: " + exception.ToString(), this);
}
helper.MakeBorder("ContentWrapper", renderingId + "DropDownWrapper", contentWrapper =>
{
helper.MakeRow("TextBorder", contentWrapper, true, textBorder => helper.MakeText("Text", textBorder, Globals.System.Texts.DefineCustomFilter, TextType.Title));
helper.MakeRow("ControlsRow", contentWrapper, true, controlsRow =>
{
helper.MakeComboBox("EventTypeComboBox", controlsRow, options ?? new List<ComboBoxItem>()
{
new ComboBoxItem()
});
helper.MakeButton("SubmitButton", controlsRow, Globals.System.Texts.Apply.Guid.ToString(), "Primary");
helper.MakeButton("ResetButton", controlsRow, Globals.System.Texts.RevertFiltersToStandard.Guid.ToString());
});
});
// Rendering
<div @rendering.HtmlAttributes>
@Html.Sitecore().Controls().Rendering(Html, Globals.Bcl.Renderings.Containers.Border, renderingId + "DropDownWrapper", "", new
{
UsePadding = true
})
</div>
}


Event type JS file

require.config({
paths: {
ExperienceAnalyticsExtended: "/sitecore/shell/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/Shared/ExperienceAnalyticsExtended",
experienceAnalyticsBase: "/sitecore/shell/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/Shared/experienceAnalyticsBase"
}
});
define(["sitecore", "ExperienceAnalyticsExtended", "experienceAnalyticsBase"], function (Sitecore, ExperienceAnalyticsExtended, ExperienceAnalyticsBase) {
Sitecore.Factories.createBaseComponent({
name: "EventTypeFilter",
base: "ExperienceAnalyticsBase",
selector: ".sc-EventTypeFilter",
attributes: Sitecore.Definitions.Views.ExperienceAnalyticsBase.prototype._scAttrs.concat([
{ name: "selectedEventTypeValue", defaultValue: null },
{ name: "selectedEventTypeName", defaultValue: null },
{ name: "errorTexts", value: "$el.data:sc-errortexts" }
]),
extendModel: {
selectedEventTypeValue: function (value) {
this.set("selectedEventTypeValue", value);
ExperienceAnalyticsExtended.setEventType(value);
}
},
initialize: function () {
this._super();
$(window).off("hashchange." + this.model.get("name"));
$(window).on("hashchange." + this.model.get("name"), _.bind(this.onHashChange, this));
},
afterRender: function () {
var appName = this.model.get("name"),
submitButton = this.app[appName + "SubmitButton"],
resetButton = this.app[appName + "ResetButton"],
EventTypeComboBox = this.app[appName + "EventTypeComboBox"];
submitButton.on("click", this.setSelectedEventType, this);
resetButton.on("click", this.resetSelectedEventType, this);
this.model.on("change:selectedEventTypeValue", function (model, value) {
EventTypeComboBox.set("selectedValue", value ? value : EventTypeComboBox.get("items")[0].itemId);
var displayFieldName = EventTypeComboBox.get("selectedItem") ?
EventTypeComboBox.viewModel.getDisplayFieldName(EventTypeComboBox.get("selectedItem")) :
null;
this.model.set("selectedEventTypeName", displayFieldName);
}, this);
this.setSelectedEventType(ExperienceAnalyticsExtended.getEventType());
},
resetSelectedEventType: function () {
this.setSelectedEventType("all");
this.closeToggleButtons();
},
setSelectedEventType: function (value) {
var appName = this.model.get("name"),
EventTypeComboBox = this.app[appName + "EventTypeComboBox"],
eventtype = value || EventTypeComboBox.get("selectedValue"),
items = EventTypeComboBox.get("items");
if (items.length === 1 && eventtype === "all") {
eventtype = items[0].itemId;
}
if (_.contains(_.pluck(items, 'itemId'), eventtype)) {
this.model.selectedEventTypeValue(eventtype);
} else {
this.showMessage("notification", this.model.get("errorTexts").InvalidSubsite, { WEBSITE: ExperienceAnalyticsExtended.getEventType() });
this.resetSelectedEventType();
}
this.closeToggleButtons();
},
onHashChange: function () {
var eventTypeFromUrl = ExperienceAnalyticsExtended.getEventTypeFromUrl();
if (this.model.get("selectedEventTypeValue") && this.model.get("selectedEventTypeValue") !== eventTypeFromUrl) {
this.model.set("selectedEventTypeValue", eventTypeFromUrl);
this.setSelectedEventType(ExperienceAnalyticsExtended.getEventType());
}
},
closeToggleButtons: function () {
var filtersModel = this.app[this.model.get("name").replace(this.model.componentName, "")];
if (filtersModel) {
filtersModel.viewModel.closeToggleButtons();
}
}
});
});

Container view and logic

Container view file:


@using Sitecore
@using Sitecore.ExperienceAnalytics.Client
@using Sitecore.ExperienceAnalytics.Core.Extensions
@using Sitecore.Mvc
@using Sitecore.Web.UI.Controls.Common.Renderings
@using Sitecore.Web.UI.Controls.Common.UserControls
@using Globals = Sitecore.ExperienceAnalytics.Client.Globals
@using UserControl = Sitecore.Web.UI.Controls.Common.UserControls.UserControl
@model Sitecore.Mvc.Presentation.RenderingModel
@{
var rendering = Html.Sitecore().Controls().GetUserControl(Model.Rendering);
rendering.Class = "sc-ExperienceAnalyticsFiltersExtended";
rendering.Requires.Script("client", "ExperienceAnalyticsFiltersExtended.js");
rendering.Attributes.Add("data-bind", "visible: isVisible");
rendering.HasNestedComponents = true;
}
@EmbedFilters(rendering)
@helper EmbedFilters(UserControl rendering)
{
var renderingId = rendering.ControlId;
var helper = new RenderingHelper(Html, renderingId);
var selectedSubsiteName = "{Binding " + renderingId + "SubsiteFilter.SelectedSubsiteName}";
var selectedEventType = "{Binding " + renderingId + "EventTypeFilter.SelectedEventTypeName}";
var fromDate = DateUtil.ToServerTime(DateTime.UtcNow).AddMonths(-1).ToDateRangeFormat();
var toDate = DateUtil.ToServerTime(DateTime.UtcNow).ToDateRangeFormat();
var dateRangeText = string.Concat(fromDate, " - ", toDate);
helper.MakeBorder("ContentWrapper", renderingId + "BarWrapper", contentWrapper =>
{
helper.MakeToggleButton("DateRangeToggleButton", contentWrapper, dateRangeText, true);
helper.MakeToggleButton("FilterToggleButton", contentWrapper, selectedSubsiteName, true);
helper.MakeToggleButton("EventTypeFilterToggleButton", contentWrapper, selectedEventType, true);
});
// Rendering
<div @rendering.HtmlAttributes>
@Html.Sitecore().Controls().Rendering(Html, Globals.Bcl.Renderings.Containers.Border, renderingId + "BarWrapper", "", new { })
@Html.Sitecore().Controls().Rendering(Html, "{208A1340-B125-46D7-9286-C4642F5B5DD4}", renderingId + "DateRangeFilter", "", new
{
IsVisible = false
})
@*Globals.Layouts.Renderings.SubsiteFilter*@
@Html.Sitecore().Controls().Rendering(Html, "{3F76EA63-6BAB-4AA7-87EB-8F4E46F524E6}", renderingId + "SubsiteFilter", "", new
{
IsVisible = false
})
@Html.Sitecore().Controls().Rendering(Html, "{55D1DA76-AC50-4476-BF34-5B968F232C2F}", renderingId + "EventTypeFilter", "", new
{
IsVisible = false
})
</div>
}


Container JS File:


require.config({
paths: {
ExperienceAnalyticsExtended: "/sitecore/shell/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/Shared/ExperienceAnalyticsExtended",
experienceAnalyticsBase: "/sitecore/shell/client/Applications/ExperienceAnalytics/Common/Layouts/Renderings/Shared/experienceAnalyticsBase"
}
});
define(["sitecore", "ExperienceAnalyticsExtended", "experienceAnalyticsBase"], function (Sitecore, ExperienceAnalyticsExtended) {
Sitecore.Factories.createBaseComponent({
name: "ExperienceAnalyticsFiltersExtended",
base: "ExperienceAnalyticsBase",
selector: ".sc-ExperienceAnalyticsFiltersExtended",
attributes: Sitecore.Definitions.Views.ExperienceAnalyticsBase.prototype._scAttrs.concat([
]),
events: {
"click .sc-togglebutton[data-sc-id*='DateRangeToggleButton']": "toggleComponents",
"click .sc-togglebutton[data-sc-id*='FilterToggleButton']": "toggleComponents",
"click .sc-togglebutton[data-sc-id*='EventTypeFilterToggleButton']": "toggleComponents"
},
initialize: function () {
var renderingId = this.model.get("name"),
dateRangeFilter = this.app[renderingId + "DateRangeFilter"],
dateRangeToggleButton = this.app[renderingId + "DateRangeToggleButton"],
subsiteFilter = this.app[renderingId + "SubsiteFilter"],
eventTypeFilter = this.app[renderingId + "EventTypeFilter"],
filterToggleButton = this.app[renderingId + "FilterToggleButton"],
eventTypeFilterToggleButton = this.app[renderingId + "EventTypeFilterToggleButton"],
subsiteComboBox = this.app[renderingId + "SubsiteFilterSubsiteComboBox"],
eventTypeComboBox = this.app[renderingId + "EventTypeFilterEventTypeComboBox"];
this.setFilterButtonState(filterToggleButton, subsiteComboBox);
this.setFilterButtonState(eventTypeFilterToggleButton, eventTypeComboBox);
this.bindComponentVisibility(filterToggleButton, subsiteFilter);
this.bindComponentVisibility(eventTypeFilterToggleButton, eventTypeFilter);
this.bindComponentVisibility(dateRangeToggleButton, dateRangeFilter);
ExperienceAnalyticsExtended.on("change:dateRange", function (m, dateRange) {
dateRangeToggleButton.set("text", dateRange.dateFrom + " - " + dateRange.dateTo);
});
ExperienceAnalyticsExtended.on("change:subsite", function (model, value) {
subsiteFilter.set("selectedSubsiteValue", value);
});
ExperienceAnalyticsExtended.on("change:eventType", function (model, value) {
eventTypeFilter.set("selectedEventTypeValue", value);
});
},
setFilterButtonState: function (toggleButton, comboBox) {
toggleButton.set("isEnabled", comboBox.get("items").length > 1);
},
bindComponentVisibility: function (button, component) {
button.on("change:isOpen", function () {
component.set("isVisible", this.get("isOpen"));
});
},
toggleComponents: function (event) {
var renderingId = this.model.get("name"),
buttonClickedId = $(event.currentTarget).attr("data-sc-id"),
buttons = ["DateRangeToggleButton", "FilterToggleButton", "EventTypeFilterToggleButton"];
for (var i = 0; i < buttons.length; i++) {
var button = this.app[renderingId + buttons[i]];
if (button.get("name") !== buttonClickedId) {
button.viewModel.close();
}
}
},
closeToggleButtons: function () {
var renderingId = this.model.get("name"),
dateRangeToggleButton = this.app[renderingId + "DateRangeToggleButton"],
filterToggleButton = this.app[renderingId + "FilterToggleButton"],
EventTypeFilterToggleButton = this.app[renderingId + "EventTypeFilterToggleButton"];
dateRangeToggleButton.viewModel.close();
filterToggleButton.viewModel.close();
EventTypeFilterToggleButton.viewModel.close();
}
});
});


All filters logic


define(["sitecore"], function (Sitecore) {
var App = Backbone.Model.extend({
cookiePrefix: "scExperienceAnalyticsExtended_",
illegalCharacters: /[/\\&%#?]/i,
attributes: {
"dateRange": null,
"subsite": null,
"eventtype": null
},
getSubsite: function() {
var subsite = "all",
subsiteFromUrl = this.getSubsiteFromUrl(),
sessionSubsite = this.getSessionValue("Subsite");
if (subsiteFromUrl) {
subsite = subsiteFromUrl;
} else if (sessionSubsite) {
subsite = sessionSubsite;
}
return subsite;
},
getEventType: function () {
var eventtype = "all",
eventtypeFromUrl = this.getEventTypeFromUrl(),
sessionEventType = this.getSessionValue("EventType");
if (eventtypeFromUrl) {
eventtype = eventtypeFromUrl;
} else if (sessionEventType) {
eventtype = sessionEventType;
}
return eventtype;
},
textTemplate: function(template, data) {
if (template && data) {
_.each(data, function (value, variableKey) {
var regexp = new RegExp("\{\{\\s*" + variableKey + "\\s*\}\}", "g");
template = template.replace(regexp, value);
});
}
return template;
},
resolveTemplatesVariables: function (templates, variables) {
_.each(templates, _.bind(function (template, key) {
templates[key] = this.textTemplate(template, variables);
}, this));
return templates;
},
setSubsite: function (value) {
value = value || "all";
this.setSessionValue("Subsite", value);
this.updateUrl({
subsite: value
});
this.set("subsite", value);
},
setEventType: function (value) {
value = value || "all";
this.setSessionValue("EventType", value);
this.updateUrl({
eventtype: value
});
this.set("eventtype", value);
},
getSubsiteFromUrl: function() {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return params.subsite;
},
getEventTypeFromUrl: function () {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return params.eventtype;
},
convertDateFormat: function(date) {
return $.datepicker.formatDate("dd-mm-yy", $.datepicker.parseDate("d M ''y", date));
},
reConvertDateFormat: function(date) {
return $.datepicker.formatDate("d M ''y", $.datepicker.parseDate("dd-mm-yy", date));
},
updateDateRangeInSession: function(from, to) {
this.setSessionValue("FromDate", this.convertDateFormat(from));
this.setSessionValue("ToDate", this.convertDateFormat(to));
},
updateDateRangeInUrl: function(from, to) {
var hashObject = {
dateFrom: this.convertDateFormat(from),
dateTo: this.convertDateFormat(to)
};
this.updateUrl(hashObject);
},
setDateRange: function (from, to, persist) {
if (persist) {
this.updateDateRangeInSession(from, to);
}
this.updateDateRangeInUrl(from, to);
this.set("dateRange", {
dateFrom: from,
dateTo: to
});
},
getDateRange: function() {
var dateRangeFromUrl = this.getDateRangeFromUrl(),
sessionFromDate = this.getSessionValue("FromDate"),
sessionToDate = this.getSessionValue("ToDate"),
dateObject = null;
if (dateRangeFromUrl.dateFrom && dateRangeFromUrl.dateTo) {
if (this.validateDate(dateRangeFromUrl.dateFrom) && this.validateDate(dateRangeFromUrl.dateTo)) {
dateObject = dateRangeFromUrl;
dateObject.dateFrom = this.reConvertDateFormat(dateObject.dateFrom);
dateObject.dateTo = this.reConvertDateFormat(dateObject.dateTo);
} else {
return undefined;
}
} else if (sessionFromDate && sessionToDate) {
dateObject = {
dateFrom: this.reConvertDateFormat(sessionFromDate),
dateTo: this.reConvertDateFormat(sessionToDate)
};
}
return dateObject;
},
validateDate: function(dateString) {
return dateString.match(/^\d{2}[\-]\d{2}[\-]\d{4}$/);
},
getDateRangeFromUrl: function() {
var hash = window.location.hash.substring(1),
params = Sitecore.Helpers.url.getQueryParameters(hash);
return {
dateFrom: params.dateFrom,
dateTo: params.dateTo
};
},
updateUrl: function (hashObject) {
var params = this.removeEmptyParams(window.location.hash.substring(1));
params = params.replace(new RegExp("^&"), "");
params = (params.length > 0) ? "?" + params : "";
params = Sitecore.Helpers.url.addQueryParameters(params, hashObject).replace("?", "");
if (window.history.replaceState) {
window.history.replaceState({ state: null }, null, window.location.pathname + window.location.search + "#" + params);
} else {
window.location.hash = params;
}
},
removeEmptyParams: function(url) {
var params = Sitecore.Helpers.url.getQueryParameters(url);
for (var p in params) {
if (params[p] === "")
url = url.replace(new RegExp("&?" + p + "="), "");
}
return url;
},
getCookie: function(name) {
var cookie = document.cookie,
cookieArray = cookie.split(";");
for (var i = 0; i < cookieArray.length; i++) {
var cookieItem = cookieArray[i].trim().split("=");
if (cookieItem[0] === name) {
return cookieItem[1];
}
}
return null;
},
getSessionValue: function (name) {
return this.getCookie(this.cookiePrefix + name);
},
// TODO Make method private and insure that it will be executed just once on the start of application
setGlobalAjaxSettings: function () {
var ajaxSettings = {},
langFromCookies = this.getCookie("shell#lang");
// Needed to improve translation mechanism on the server
if (langFromCookies) {
ajaxSettings.headers = { "Accept-Language": langFromCookies };
}
$.ajaxSetup(ajaxSettings);
},
setSessionValue: function(name, value) {
document.cookie = this.cookiePrefix + name + "=" + value + ";path=/;";
},
/**
* Return first invalid string which matches with validator
* @param {array|string} list - array of strings which should be validated
* @param {RegExp} validator - regexp to find invalid sub-strings or characters
* @return {string|null} returns first invalid sub-strings or character if found or null if invalid character is not found
*/
findInvalidString: function (list, validator) {
if (typeof list === 'string') {
list = [list];
}
var invalidString = _.find(list, function (str) {
return validator.test(str);
});
if (invalidString) {
return invalidString.match(validator)[0];
} else {
return null;
}
}
});
var app = new App;
app.setGlobalAjaxSettings();
return app;
});


Hope the above gave a clear idea of how you can extend sitecore experience analytics filters.

3 comments:

Sasha said...

Thank you, it was helpful! One piece of information that is not clear to me - when the filter is set in place, where does the application respond to update the graph to make use of the new filter?

Unknown said...

I have implemented this in my current project, however, it is not working(Filter is not getting appeared on Sitecore Analytics Reports Dashboard page). I have followed all above mentioned steps, but no luck. Can you please explain it more with actual screen shot of Sitecore Analytics Reports Dashboard.

sri said...

Thanks for sharing this.,
Leanpitch provides online training in CSPO during this lockdown period everyone can use it wisely.
Join Leanpitch 2 Days CSPO Certification Workshop in different cities.

Product owner certification

Post a Comment