Saturday, March 11, 2017

Sitecore showing custom contact facet data in Experience Profiles

This post is part 3 of a series, the introduction is available here and includes a table of contents, along with the full source code.

Add the custom data tab to the Experience Profile

Adding the tab to Sitecore's Experience Profile area, requires creating items in the Core database. This task cannot be done via the standard web interface and instead will have to be done through Sitecore Rocks.

This task is similar to building up a standard page in Sitecore. A base item is created which contains a layout and a number of sublayouts/renderings which then build up what is displayed on the page. However instead of it being a standalone page, it's another tab which appears inside the experience profile.

This post is based heavily on the example/steps provided by Adam Conn, in this post. The primary addition is the option to display listed data (in a table) as opposed to single strings, along with some changes in rendering names.
  1. Expand the core database and locate the PageSettings item: /sitecore/client/Applications/ExperienceProfile/Contact/PageSettings
  2. Insert a new item under PageSettings of the type Page-Stylesheet-File an
    1. Set the name to CustomDataTab
    2. Set the Stylesheet field to /sitecore/shell/client/Applications/ExperienceProfile/Contact/CustomData/CustomDataTab.css - this file will be created in a later step.
  3. Locate the Tabs item: /sitecore/client/Applications/ExperienceProfile/Contact/PageSettings/Tabs
  4. Insert a new item of template tab and call it CustomData


  5. On this newly created item, change the header fields text to Custom Data
  6. Insert another item of type tab under the CustomData item and all this CustomDataPanel
  7. Right click on the CustomDataPanel item and select Tasks Design Layout


  8. Set the layout field to the type Speak-EmptyLayout

  9.  
  10. Right click on the CustomData item and select Tasks > Design Layout
  11. Add a new rendering of type text
    1. Edit this item and set IsVisible to false
    2. Set the ID to CustomDataTabId
    3. Set the Text field to be the item ID of the CustomData item



  12. Add a border rendering and set the ID to CustomDataTabBorder
  13. Add a LoadOnDemandPanel rendering and edit the following fields
    1. Set PlaceholderKey to CustomDataTabBorder.Content
    2. Set Database to core
    3. Set ItemId to the item ID of the CustomDataPanel
    4. Set ID to CustomDataLoadOnDemandPanel


  14. Add an image rendering and edit the following fields
    1. Set width to 60px
    2. Set height to 60px
    3. Set IsVisible to {Binding CustomDataLoadOnDemandPanel.isLoaded, Converter=Not}
    4. Set PlaceholderKey to CustomDataTabBorder.Content
    5. Set ID to CustomDataIndicatorImage
  15. Add a ProgressIndicator rendering and edit the following fields
    1. Set ID to CustomDataProgressIndicator
    2. Set IsBusy to {Binding CustomDataLoadOnDemandPanel.isBusy}
    3. Set DataSource to {88134D50-FA78-406D-8C52-8286EB12BB25} - this is a system item for the progress indicator
    4. Set PlaceholderKey to CustomDataTabBorder.Content
    5. Set TargetControl to CustomDataTabBorder
  16. The design layout for CustomData should now appear the same as the following:


  17. Insert under the CustomDataPanel item a new item of type AdvancedExpander Parameters (may be called Accordion Parameters in older versions). Call this item, CustomDataAccordion.
    1. Set the header field of this item to Custom Data.


  18. Insert under the CustomDataPanel item a new item of type Text Parameters, and call this item DataNotFound.
    1. Set the text field of this new item to be "No custom data has been found". This is the message that will display when a given contact has no data in the custom contact facet.
  19. Right click on the CustomDataPanel item and select Tasks Design Layout
  20. Add a new rendering of type SubPageCode.
    1. Ensure the PlaceholderKey is Page.Body
    2. Set PageCodeScriptFileName to "/sitecore/shell/client/Applications/ExperienceProfile/Contact/CustomData/CustomDataTab.js" - this file will be created in a later step.
  21. Add a new rendering of type border.
    1. Give it an id of CustomDataTabBorder.
    2. Ensure the PlaceholderKey is Page.Body
  22. Add a new rendering of type MessageBar.
    1. Give it an id of ExternalDataTabMessageBar
    2. Ensure the PlaceholderKey is Page.Body
  23. Add a new rendering of type text.
    1. Give it an id of NoCustomData.
    2. Set IsVisible to false.
    3. Set the text to No Custom Data Found
    4. Set the PlaceholderKey to CustomDataTabBorder.Content
  24. Add a new rendering of type GenericDataProvider
    1. Set the PlaceholderKey to CustomDataTabBorder.Content
    2. Give it an id of CustomDataProvider
  25. Add a new rendering of type Border
    1. Set the PlaceholderKey to CustomDataTabBorder.Content
    2. Give it an id of CustomDataBorder
  26. Add a new rendering of type AdvancedExpander (in older versions of Sitecore it will be accordion)
    1. Set the PlaceholderKey to CustomDataBorder.Content
    2. Give it an id of CustomDataExpander
    3. Set the datasource to the item id of the CustomDataAccordion item created earlier.
  27. Add a new rendering of type border
    1. Set the UsePadding field to True
    2. Set the PlaceholderKey to CustomDataExpander.Body
    3. Set the id to CrmIdBorder
  28. Add a new rendering of type text
    1. Set the text value to CRM ID: 
    2. Set the PlaceholderKey to CrmIdBorder.Content
    3. Give it an id of CrmIdLabel
  29. Add a new rendering of type text 
    1. Set the PlaceholderKey to CrmIdBorder.Content
    2. Give it an id of CrmIdValue
  30. Under the CustomDataPanel item insert a new item of type ListControl
    1. Give it an id of ProductPurchaseList
  31. Under the ProductPurchaseList insert a new item of ColumnField type
    1. Give it an id of Product
    2. Set the header text to Product ID
    3. Set the data field to Product
  32. Under the ProductPurchaseList insert a new item of ColumnField type
    1. Give it an id of Date
    2. Set the header text to Purchase Date
    3. Set the data field to Date
  33. Add a new rendering of type border
    1. Set the UsePadding field to True
    2. Set the PlaceholderKey to CustomDataExpander.Body
    3. Set the id to ProductPurchaseBorder
  34. Add a new rendering of type text
    1. Set the text value to Purchase History: 
    2. Set the PlaceholderKey to ProductPurchaseBorder.Content
    3. Give it an id of ProductPurchaseLabel
  35. Add a new rendering of type SearchDataSource
    1. Set the id to ProductPurchaseDataSource
    2. Set the PlaceholderKey to ProductPurchaseBorder.Content
  36. Add a new rendering of type ListControl
    1. Set the id of to ProductPurchaseListControl
    2. Set the PlaceholderKey to ProductPurchaseBorder.Content
    3. Set the DataSource to the id of the ProductPurchaseList item created earlier
    4. Set the value of the Binding field to {Binding ProductPurchaseDataSource.Items}
The CustomDataPanel design layout will now look like the following.


The item tree for the custom tab will look like this.


Create the query pipeline

In order to display the data from the Custom Data facet in the experience profile a query pipeline processor needs to be created which will query xDB to retrieve the data.

using Sitecore.Cintel.Reporting;
using Sitecore.Cintel.Reporting.Processors;
using Sitecore.Cintel.Reporting.ReportingServerDatasource;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace ContactFacet.Reporting
{
    public class GetCustomData : ReportProcessorBase
    {
        public override void Process(ReportProcessorArgs args)
        {
            var queryExpression = this.CreateQuery().Build();
            var table = base.GetTableFromContactQueryExpression(queryExpression, args.ReportParameters.ContactId, null);
            args.QueryResult = table;
        }
        protected virtual QueryBuilder CreateQuery()
        {
            var builder = new QueryBuilder
            {
                collectionName = "Contacts"
            };
            builder.Fields.Add("_id");
            builder.Fields.Add("CustomData_CrmId");
            builder.Fields.Add("CustomData_ActiveCustomer");
            builder.QueryParms.Add("_id", "@contactid");
            return builder;
        }
    }
}

Create the contact view pipeline

The next step is to create the pipeline processor that will be used by the experience profile to execute a query and place the results into a data object. In this case the data will be stored inside a System.Data.DataTable which will ultimately be serialized into an array to be used via the display JavaScript file.

The first step is to define the results data table structure.

using Sitecore.Cintel.Reporting;
using Sitecore.Cintel.Reporting.Processors;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;

namespace ContactFacet.Reporting
{
    public class ConstructCustomDataTable : ReportProcessorBase
    {
        public override void Process(ReportProcessorArgs args)
        {
            args.ResultTableForView = new DataTable();

            var viewField = new ViewField<string>("CrmId");
            args.ResultTableForView.Columns.Add(viewField.ToColumn());

            var customViewField = new ViewField<bool>("ActiveCustomer");
            args.ResultTableForView.Columns.Add(customViewField.ToColumn());

            var productViewField = new ViewField<DataTable>("ProductPurchases");
            args.ResultTableForView.Columns.Add(productViewField.ToColumn());
        }
    }
}

The next step is to create the pipeline which will execute the query and populate the data table defined above.

using ContactFacet.Custom;
using Sitecore.Cintel;
using Sitecore.Cintel.Reporting;
using Sitecore.Cintel.Reporting.Processors;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;

namespace ContactFacet.Reporting
{
    public class PopulateCustomData : ReportProcessorBase
    {
        public override void Process(ReportProcessorArgs args)
        {
            ICustomDataFacet fullFacet = (ICustomDataFacet)CustomerIntelligenceManager.ContactService.GetFacet(args.ReportParameters.ContactId, "CustomData");
            var result = args.QueryResult;
            var table = args.ResultTableForView;

            foreach (DataRow row in result.AsEnumerable())
            {
                var targetRow = table.NewRow();

                // CRM ID
                var id = row["CustomData_CrmId"];

                if (id == null || string.IsNullOrEmpty(id.ToString()))
                {
                    continue;
                }
                
                targetRow["CrmId"] = id;

                // Active Customer
                var activeCustomer = row["CustomData_ActiveCustomer"];

                if (activeCustomer == null || string.IsNullOrEmpty(id.ToString()))
                {
                    continue;
                }

                targetRow["ActiveCustomer"] = Convert.ToBoolean(activeCustomer);

                // Product Purchases
                DataTable productPurchases = new DataTable();
                productPurchases.Columns.Add("Product", typeof(string));
                productPurchases.Columns.Add("Date", typeof(DateTime));

                if (fullFacet.ProductPurchases != null && fullFacet.ProductPurchases.Any())
                {
                    foreach (var purchase in fullFacet.ProductPurchases)
                    {
                        productPurchases.Rows.Add(purchase.ProductId, purchase.PurchaseDate);
                    }
                }

                targetRow["ProductPurchases"] = productPurchases;

                table.Rows.Add(targetRow);
            }

            args.ResultSet.Data.Dataset[args.ReportParameters.ViewName] = table;
        }
    }
}

Register the pipelines

The following configuration file will regster the three pipelines above inside Sitecore.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <group groupName="ExperienceProfileContactDataSourceQueries">
        <pipelines>
          <custom-query>
            <processor type="ContactFacet.Reporting.GetCustomData, ContactFacet" />
          </custom-query>
        </pipelines>
      </group>
      <group groupName="ExperienceProfileContactViews">
        <pipelines>
          <custom>
            <processor type="ContactFacet.Reporting.ConstructCustomDataTable, ContactFacet" />
            <processor type="Sitecore.Cintel.Reporting.Processors.ExecuteReportingServerDatasourceQuery, Sitecore.Cintel">
              <param desc="queryName">custom-query</param>
            </processor>
            <processor type="ContactFacet.Reporting.PopulateCustomData, ContactFacet" />
          </custom>
        </pipelines>
      </group>
    </pipelines>
  </sitecore>
</configuration>

Create JavaScript to call the pipelines and display data

Earlier on a JavaScript file was referenced with the following path "/sitecore/shell/client/Applications/ExperienceProfile/Contact/CustomData/CustomDataTab.js" in the code solution create this file with that folder path.
define(
  ["sitecore",
    "/-/speak/v1/experienceprofile/DataProviderHelper.js",
    "/-/speak/v1/experienceprofile/CintelUtl.js"
  ],
  function (sc, providerHelper, cintelUtil, ExternalDataApiVersion) {
      var cidParam = "cid";
      var intelPath = "/intel";

      var app = sc.Definitions.App.extend({
          initialized: function () {
              $('.sc-progressindicator').first().show().hide();
              var contactId = cintelUtil.getQueryParam(cidParam);
              var tableName = "";
              var baseUrl = "/sitecore/api/ao/v1/contacts/" + contactId + "/intel/custom";

              providerHelper.initProvider(this.CustomDataProvider,
                tableName,
                baseUrl,
                this.ExternalDataTabMessageBar);

              providerHelper.getData(this.CustomDataProvider,
                $.proxy(function (jsonData) {
                    var dataSetProperty = "Data";
                    if (jsonData.data.dataSet != null && jsonData.data.dataSet.custom.length > 0) {
                        // Data present set value content
                        var dataSet = jsonData.data.dataSet.custom[0];
                        this.CustomDataProvider.set(dataSetProperty, jsonData);
                        this.CrmIdValue.set("text", dataSet.CrmId);
                        this.ProductPurchaseDataSource.set('items', dataSet.ProductPurchases);
                    } else {
                        // No data hide the labels
                        this.CrmIdLabel.set("isVisible", false);
                        this.ProductPurchaseLabel.set("isVisible", false);
                        this.ExternalDataTabMessageBar.addMessage("notification", this.NoCustomData.get("text"));
                    }
                }, this));
          }
      });
      return app;
  });

Create the CSS styles

By default the tab will not look the greatest in terms of padding. Below is a basic starter to show how you can style he custom data tab.

div[data-sc-id=CustomDataExpander] {
  margin-top: 30px;
}

div[data-sc-id=CustomDataExpander] .sc-advancedExpander-body {
    padding:10px;
}

div[data-sc-id=CustomDataExpander] .sc-border {
    margin-top:10px;
    margin-bottom:10px;
}

The end result

Now the custom data tab will appear inside the experience profile and star displaying as expected.


Part four: Sitecore merging contacts with custom facets goes over how this custom facet data is handled when two contacts are merged.

No comments:

Post a Comment