LibrarySites.Banner

Using Custom Contact Data Part 1 - Experience Profile

Starting with Sitecore 7.5 an individual visitor is represented using a contact. Information that is collected about the contact is stored in contact facets. This part 1 in a 3-part series that explores how data stored in contact facets can be used throughout the Sitecore Experience Platform.

In this post I will cover how data stored in contact facets can be exposed in the Experience Profile, also known as the xFile.

Prerequisites

In my previous post I covered how to get custom contact data into xDB using contact facets. This post assumes you have read that post.

Important Note

At the time I wrote this post documentation for SPEAK and the Experience Profile were not available. The instructions and information below is accurate to the best of my knowledge, but:

  • There are probably better ways to do some of this stuff
  • Some of the components or APIs I used may be deprecated by the time you read this
  • Even though the code works as described, I may be including unnecessary components or APIs, and even using some of them wrong

In other words, use this information to help get familiar with how things work, but don't take it as a recommendation or as an example of any "best practices"!

Introduction to Experience Profile

Experience Profile, or xFile, is a Sitecore application that displays contact data to marketers. It includes a lot of the information you are used to working with if you've used DMS. But it also can be extended in order to include custom data.

xFile is built using the SPEAK user interface framework. In case you haven't heard of SPEAK, it is the replacement for SheerUI. SPEAK includes a huge collection of UI components that can be used to build a variety of user interfaces on the Sitecore platform. It leverages modern browser technology and architecture.

(I expect someone from the community to emerge as the SPEAK authority. And I expect it will not be me.)

The rest of this post explains how to add a new tab to xFile that will display the employee data that I captured in my previous post.

Step 1 - Add Assembly References to Visual Studio Project

Add the following references to the Visual Studio Project:

  • Sitecore.Cintel.dll

Step 2 - Add Javascript Code File

Javascript code files are used to populate the user interface components in SPEAK applications like xFile. My code file needs to request the employee data I want to display in xFile, and then it needs to assign the employee data to the appropriate user interface components so the data is visible.

Create the file /sitecore/shell/client/Applications/ExperienceProfile/Contact/Employee/EmployeeTab.js and add the following code:

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/employee";
 
        providerHelper.initProvider(this.EmployeeDataProvider,
          tableName,
          baseUrl,
          this.ExternalDataTabMessageBar);
 
        providerHelper.getData(this.EmployeeDataProvider,
          $.proxy(function (jsonData) {
            var dataSetProperty = "Data";
            if (jsonData.data.dataSet != null && jsonData.data.dataSet.employee.length > 0) {
              var dataSet = jsonData.data.dataSet.employee[0];
              this.EmployeeDataProvider.set(dataSetProperty, jsonData);
              this.EmployeeIdValue.set("text", dataSet.EmployeeId);
            } else {
              this.EmployeeIdLabel.set("isVisible", false);
              this.ExternalDataTabMessageBar.addMessage("notification", this.NoEmployeeData.get("text"));
            }
          }, this));
      }
    });
    return app;
  });

Step 3 - Add Employee Tab to xFile

SPEAK applications are configured in Sitecore's core database using Sitecore items. This includes which user interface components make up the application. This design makes it very easy to extend the Sitecore client and to build new applications.

Now, if you checked the length of this post - especially this particular session- my description of it being "very easy" to extend the Sitecore client may seem ridiculous. I maintain this is true. Adding a new component can be as easy as adding a rendering to the presentation details of a panel in an application.

However, if you are adding significant new functionality, such as a completely new tab to xFile, there are a lot of components you need to configure. Once you get familiar with the tool kit it is not nearly as overwhelming as it first seems. Give yourself some time to learn it. The payoff will be tremendous.

One final word before I start configuring the employee data tab: Sitecore Rocks must be used to do this configuration. Even though I am using configuring Sitecore items and renderings, the Sitecore client does not provide the environment needed to configure everything that goes into a SPEAK application. I can use the Sitecore Rocks extension for Visual Studio or Sitecore Rocks Windows.

  1. Add the following item:
    • Database: core
    • Parent item: /sitecore/client/Applications/ExperienceProfile/Contact/PageSettings/Tabs
    • Template: Tab
    • Item name: Employee
  2. Note the item ID for the Employee item. This ID will be needed in a later step.
  3. Set the following field value on the item:
    • Field name: Header
    • Value: Employee Data
  4. Add the following item as a child of the Employee item:
    • Template: Tab
    • Item name: EmployeePanel
  5. Note the item ID for the EmployeePanel item. This ID will be needed in a later step.
  6. Right-click the Employee item and select Open Tasks > Design Layout
  7. Add the following rendering:
    • Type: Text
    • IsVisible: False
    • Text: [Employee item ID]
    • ID: EmployeeDataTabId
  8. Add the following rendering:
    • Type: Border
    • ID: EmployeeDataTabBorder
  9. Add the following rendering
    • Type: LoadOnDemandPanel
    • PlaceholderKey: EmployeeDataTabBorder.Content
    • Database: core
    • ItemId: [EmployeePanel item ID]
    • ID: EmployeeDataLoadOnDemandPanel
  10. Add the following rendering:
    • Type: Image
    • Height: 60px
    • IsVisible: {Binding EmployeeDataLoadOnDemandPanel.isLoaded, Converter=Not}
    • Width: 60px
    • PlaceholderKey: EmployeeDataTabBorder.Content
    • ID: EmployeeDataIndicatorImage
  11. Add the following rendering:
    • Type: ProgressIndicator
    • IsBusy: {Binding EmployeeDataLoadOnDemandPanel.isBusy}
    • DataSource: {88134D50-FA78-406D-8C52-8286EB12BB25}
    • PlaceholderKey: EmployeeDataTabBorder.Content
    • TargetControl: EmployeeTabBorder
    • AutoShowTimeout: 1000
    • ID: EmployeeDataProgressIndicator
  12. Add the following item as a child of the EmployeePanel item:
    • Template: Accordion Parameters
    • Item name: EmployeeDataAccordion
  13. Note the item ID for the EmployeeDataAccordion item. This ID will be needed in a later step.
  14. Set the following field value on the item:
    • Field name: Header
    • Value: Employee Data
  15. Add the following item as a child of the EmployeePanel item:
    • Template: Text Parameters
    • Item name: NoDataFound
  16. Note the item ID for the NoDataFound item. This ID will be needed in a later step.
  17. Set the following field value on the item:
    • Field name: Text
    • Value: No data was found
  18. Right-click the EmployeePanel item and select Open Tasks > Design Layout
  19. Select the layout Speak-EmptyLayout
  20. Add the following rendering:
    • Type: SubPageCode
    • PlaceholderKey: Page.Body
    • PageCodeScriptFileName: /sitecore/shell/client/Applications/ExperienceProfile/Contact/Employee/EmployeeTab.js
  21. Add the following rendering:
    • Type: Border
    • PlaceholderKey: Page.Body
    • ID: EmployeeDataTabBorder
  22. Add the following rendering:
    • Type: MessageBar
    • PlaceholderKey: Page.Body
    • ID: ExternalDataTabMessageBar
  23. Add the following rendering:
    • Type: Text
    • IsVisible: False
    • Text: No employee data found
    • PlaceholderKey: EmployeeDataTabBorder.Content
    • ID: NoEmployeeData
  24. Add the following rendering:
    • Type: GenericDataProvider
    • PlaceholderKey: EmployeeDataTabBorder.Content
    • ID: EmployeeDataProvider
  25. Add the following rendering:
    • Type: Border
    • PlaceholderKey: EmployeeDataTabBorder.Content
    • ID: EmployeeBorder
  26. Add the following rendering:
    • Type: Accordion
    • DataSource: [EmployeeDataAccordion item ID]
    • PlaceholderKey: EmployeeBorder.Content
    • ID: EmployeeAccordion
  27. Add the following rendering:
    • Type: Border
    • UsePadding: checked
    • PlaceholderKey: EmployeeAccordion.Body
    • ID: EmployeeIdBorder
  28. Add the following rendering:
    • Type: Text
    • Text: Employee #:
    • PlaceholderKey: EmployeeIdBorder.Content
    • ID: EmployeeIdLabel
  29. Add the following rendering:
    • Type: Text
    • PlaceholderKey: EmployeeIdBorder.Content
    • ID: EmployeeIdValue

Step 4 - Implement Query Pipeline Processors

Query pipelines are used to retrieve the data (from xDB) that will be displayed in xFile. Each query has its own pipeline. I need a query that will read my employee data.

The Sitecore.Cintel.Reporting.ReportingServerDatasource.QueryBuilder class is used to define the query.

Add the following type to the Visual Studio project:

using System;
using System.Collections.Generic;
using System.Linq;
using Sitecore.Cintel.Reporting.Processors;
using Sitecore.Cintel.Reporting;
using Sitecore.Cintel.Reporting.ReportingServerDatasource;
 
namespace Testing.ContactFacets.Reporting
{
    public class GetEmployeeData : 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("EmployeeData_EmployeeId");
            builder.QueryParms.Add("_id", "@contactid");
            return builder;
        }
    }
}

Step 5 - Implement Contact View Pipeline Processors

xFile consolidates data from a lot of different places. It does this by executing queries to read the data which it displays.

Just like each query has a pipeline that controls how the query is built, the execution of each query and the handling of the results of the each is handled by a pipeline. That is what the contact view pipelines do.

My contact view pipeline involves 3 steps:

  • Creating a data structure to hold the results of the query
  • Executing the query
  • Populating the data structure with the results of the query

    Sitecore provides a processor that will execute the query, so I only need to worry about creating and populating the data structure.

    Add the following type to the Visual Studio project. This processor creates a System.Data.DataTable. This is the data structure:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Sitecore.Cintel.Reporting.Processors;
    using Sitecore.Cintel.Reporting;
    using System.Data;
    using Testing.ContactFacets.Model;
     
    namespace Testing.ContactFacets.Reporting
    {
        public class ConstructEmployeeDataTable : ReportProcessorBase
        {
            public override void Process(ReportProcessorArgs args)
            {
                args.ResultTableForView = new DataTable();
                var viewField = new ViewField<string>("EmployeeId");
                args.ResultTableForView.Columns.Add(viewField.ToColumn());
            }
        }
    }

    Add the following type to the Visual Studio project. This processor populates the DataTable with the results of the query:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Sitecore.Cintel.Reporting.Processors;
    using Sitecore.Cintel.Reporting;
    using System.Data;
     
    namespace Testing.ContactFacets.Reporting
    {
        public class PopulateWithEmployeeData : ReportProcessorBase
        {
            public override void Process(ReportProcessorArgs args)
            {
                var result = args.QueryResult;
                var table = args.ResultTableForView;
                if (table.Columns.Contains("EmployeeId"))
                {
                    foreach (DataRow row in result.AsEnumerable())
                    {
                        var id = row["EmployeeData_EmployeeId"];
                        if (id == null || string.IsNullOrEmpty(id.ToString()))
                        {
                            continue;
                        }
                        var targetRow = table.NewRow();
                        targetRow["EmployeeId"] = id;
                        table.Rows.Add(targetRow);
                    }
                }
                args.ResultSet.Data.Dataset[args.ReportParameters.ViewName] = table;
            }
        }
    }

    Step 6 - Register the Pipelines

    Sitecore needs to be configured to use my new pipelines.

    I need to add the following configuration into my config file. This markup goes inside the /configuration/sitecore node.

    <pipelines>
      <group groupName="ExperienceProfileContactDataSourceQueries">
        <pipelines>
          <employee-query>
            <processor type="Testing.ContactFacets.Reporting.GetEmployeeData, Testing.ContactFacets" />
          </employee-query>
        </pipelines>
      </group>
      <group groupName="ExperienceProfileContactViews">
        <pipelines>
          <employee>
            <processor type="Testing.ContactFacets.Reporting.ConstructEmployeeDataTable, Testing.ContactFacets" />
            <processor type="Sitecore.Cintel.Reporting.Processors.ExecuteReportingServerDatasourceQuery, Sitecore.Cintel">
              <param desc="queryName">employee-query</param>
            </processor>
            <processor type="Testing.ContactFacets.Reporting.PopulateWithEmployeeData, Testing.ContactFacets" />
          </employee>
        </pipelines>
      </group>
    </pipelines>

    Step 7 - Deploy the Component

    I need to compile my code and deploy my assembly, config file and Javascript file to my Sitecore server.

    Confirming the Component Works

    I can confirm my new tab works properly by opening xFile. A new tab appears for employee data.

    If I view a contact with employee data stored in the contact will see the data.

    If I view a contact without employee data stored in the contact I will see a message that reports this.

    Next Steps

    This was a really long post. Congratulations for making it to the end. While this example is a very basic one, hopefully it helps get you interested in exploring xFile and figuring out how to add your own extensions.

    If attended Sitecore Symposium 2014 you may have seen a real-world example of extending xFile to include third-party data. That code will be made available soon, probably as a shared source module.

  • Thanks a bunch for this Adam! You inspired a cool POC that I attempted to present on during the Sitecore Users Virtual Summit. My demo environment failed me so I put together this video after.  http://youtu.be/RTw9oiF-Msw

  • Thanks Adam, very useful.  Just a small mistake regarding your previous article : in the query builder you use the row name "EmployeeData_EmployeeId" but in the previous article you created a facet with the name "Employee Data" so no result. Just need to remove the space.

  • Hey Adam,  Great series of posts!  Can I get your thoughts on the use case of storing profile data in xDB as a facet? One of the use cases I'm exploring is CRM integration but, if I'm following correctly, this information will only be current when the user identifies (register/login) on the front end. In the call-centre type scenario, any "interactions" or updates to the users information in the CRM will mean Exp. Profile will not have current information until the user identifies again and the updated info is re-indexed.  Do you think a better approach for this sort of integration would be to source data directly from CRM using a custom speak component for display in Exp. profile rather than populating fields in xDB?

  • this article could do with less marketing Jargon:  Experience Profile, xDB, SPEAK,  xFile, DMS, SheerUI  Why not just say what it is instead of using it's marking term? I could do with a sitecore dictionary of buzz words to translate all of this.

  • Is it possible to add custom facets on existing tab instead of creating as new tab?

  • Yes... I can able to add custom facets on existing tab. Thanks for your Article.

  • Thanks for the article, I am having trouble with step 26/27 there do not seem to be any Accordion renders...   I am new to Sitecore (8.) apologies if it is a foolish question....

  • "Accordion Parameters" appears to have been renamed to "AdvancedExpander Parameters" in newer versions of Sitecore.

  • Hello,

    Great post Adam !!

    Pretty much mostly what current Facets and Reporting Processors do.

    There is however a small issue with this implementation.

    When the query is built with :

    builder.Fields.Add("EmployeeData_EmployeeId");

    If you go by the post in the " Introducing Contact Facets". It gives me no record when the query is ran

    ( i.e GetTableFromContactQueryExpression)

    This is because the previous post suggests to create the Facet name as "Employee Data" (i.e. with an " " between Employee and data)

    however, the query says "EmployeeData_EmployeeId". I was able to fix my issue by introducing a sapce in the query.

    P.S :  Using 8.2 update 4.

    I will be glad to hear if I was not right in doing what I did. :)

    Regards

    Akshansh