allBlogsList

View custom facets values from within Sitecore Experience Profile

September 23rd, 2019

I noticed many articles that talk about something similar but not very clear. In other words, I struggled through this and thought of making this blog to help others. The target then is to display some of our custom facet fields/values in a new tab under Experience profile for a contact. So here we go.

To give a background, following are snapshots of my custom facet configurations. These are code samples and configurations from the gigya connector Gigya Connector in Marketplace I wrote that now has this feature of viewing facets within Experience profile.

<model>  
      <elements>  
        <element patch:after="element\[@interface='Sitecore.Analytics.Model.Entities.IContactPreferences, Sitecore.Analytics.Model'\]"  
            interface="GigyaSecurityProvider.Facets.Contracts.IGigyaElement, GigyaSecurityProvider"  
            implementation="GigyaSecurityProvider.Facets.Implementation.GigyaElement, GigyaSecurityProvider" />  
        <element patch:after="element\[@interface='Sitecore.Analytics.Model.Entities.IContactPreferences, Sitecore.Analytics.Model'\]"  
            interface="GigyaSecurityProvider.Facets.Contracts.IGigyaFacet, GigyaSecurityProvider"  
            implementation="GigyaSecurityProvider.Facets.Implementation.GigyaFacet, GigyaSecurityProvider" />  
      </elements>  
      <entities>  
        <contact>  
          <facets>  
            <facet patch:after="facet\[@name='Preferences'\]" name="Gigya"  
                contract="GigyaSecurityProvider.Facets.Contracts.IGigyaFacet, GigyaSecurityProvider" />  
          </facets>  
        </contact>  
      </entities>  
    </model>  
    

 As you can see here, I have a simple custom facet "Gigya" defined that, if you see below, contains collection of field/value.

namespace GigyaSecurityProvider.Facets.Contracts  
{  
    public interface IGigyaFacet : IFacet  
    {  
        IElementCollection<IGigyaElement> GigyaFields { get; }  
    }  
}  
  
    public interface IGigyaElement : IElement  
    {  
        string Field { get; set; }  
        string Value { get; set; }  
    }

1- In this same configuration file you should now add these extra pipeline settings

<pipelines>  
      <group groupName="ExperienceProfileContactDataSourceQueries">  
        <pipelines>  
          <custom-query>  
            <processor type="GigyaSecurityProvider.ExperienceProfile.GetCustomData, GigyaSecurityProvider" />  
          </custom-query>  
        </pipelines>  
      </group>  
      <group groupName="ExperienceProfileContactViews">  
        <pipelines>  
          <gigya>  
            <processor type="GigyaSecurityProvider.ExperienceProfile.ConstructCustomDataTable, GigyaSecurityProvider" />  
            <processor type="Sitecore.Cintel.Reporting.Processors.ExecuteReportingServerDatasourceQuery, Sitecore.Cintel">  
              <param desc="queryName">custom-query</param>  
            </processor>  
            <processor type="GigyaSecurityProvider.ExperienceProfile.PopulateCustomData, GigyaSecurityProvider" />  
          </gigya>  
        </pipelines>  
      </group>  
    </pipelines>  
     

Note that the name of the pipeline ("gigya") is same as the custom facet defined earlier. Also this ("gigya") would act as the default table name and tab name, that will contain the rows/columns to be displayed within this tab. Following are the code snapshots for "ConstructCustomDataTable", "PopulateCustomData" and "GetCustomData". If you review the code ("GetCustomData"), "custom-query" is where we define the query that should run to fetch the current contact's table of information. In "ConstructCustomDataTable" we create this custom column "GigyaFields" in the table for view. This column ("GigyaFields") is filled with one or more field/value pairs in "PopulateCustomData".

2- Following are code examples for these namespaces defined

namespace GigyaSecurityProvider.ExperienceProfile  
{  
/// <summary>  
/// Base processor class for Contact custom tab in experience profile  
/// </summary>  
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.QueryParms.Add("\_id", "@contactid");  
return builder;  
}  
}  
}  
  
  
namespace GigyaSecurityProvider.ExperienceProfile  
{  
/// <summary>  
/// Base table class for experience profile custom tab  
/// </summary>  
public class ConstructCustomDataTable : ReportProcessorBase  
{  
public override void Process(ReportProcessorArgs args)  
{  
args.ResultTableForView = new DataTable();  
  
var gigyaViewField = new ViewField<DataTable>("GigyaFields");  
args.ResultTableForView.Columns.Add(gigyaViewField.ToColumn());  
}  
}  
}  
  
  
namespace GigyaSecurityProvider.ExperienceProfile  
{  
/// <summary>  
/// Processor class to fill Contact custom facet fields from Mongo to custom tab in experience profile  
/// </summary>  
public class PopulateCustomData : ReportProcessorBase  
{  
public override void Process(ReportProcessorArgs args)  
{  
IGigyaFacet fullFacet = (IGigyaFacet)CustomerIntelligenceManager.ContactService.GetFacet(args.ReportParameters.ContactId, "Gigya");  
var result = args.QueryResult;  
var table = args.ResultTableForView;  
  
var targetRow = table.NewRow();  
  
// Gigya Fields  
DataTable gigyaFields = new DataTable();  
gigyaFields.Columns.Add("Field", typeof(string));  
gigyaFields.Columns.Add("Value", typeof(string));  
  
if (fullFacet.GigyaFields != null && fullFacet.GigyaFields.Any())  
{  
foreach (var field in fullFacet.GigyaFields)  
{  
gigyaFields.Rows.Add(field.Field, field.Value);  
}  
}  
  
targetRow\["GigyaFields"\] = gigyaFields;  
  
table.Rows.Add(targetRow);  
  
args.ResultSet.Data.Dataset\[args.ReportParameters.ViewName\] = table;  
}  
}  
}

Now that we have seen the code, let us switch to other steps. 

3- You will need Sitecore rocks to create these. Through Sitecore rocks explorer, connect to CORE database and go to "/sitecore/client/Applications/ExperienceProfile/Contact/PageSettings/Tabs".

4- You can then create a duplicate of an existing item like "/sitecore/client/Applications/ExperienceProfile/Contact/PageSettings/Tabs/Details" and name it as per your requirement. Since I wrote this as part of our gigya connector extension, I named it as "Gigya". You will hence see "Gigya" as a new tab in the contact's profile under Experience profile in sitecore.

5- In Sitecore explorer, right-click on "DetailsPanel" under "Gigya" and add an item of type "SearchDataSource".

6- In Sitecore explorer, right-click on "DetailsPanel" under "Gigya" and add an item of type "ListControl". 

7- Right-click on this created "ListControl" and add two "ColumnField" types. 

8- Double click on these column field items and enter a proper value for the "HeaderText" fields. In my case I entered "Gigya Field" and "Value".

9- Create a folder under "...\Website\sitecore\shell\client\Applications\ExperienceProfile\Contact", giving the same name as provided in step 2 (in my case "Gigya").

10- In this folder, create a js file as below ("gigyatab.js"). This js effectively wires up all the Renderings we have added, retrieve all the data from xDB from the Pipelines we created in the previous section and passes it to the DataProvider. The function "setupContactGigyaDetails" is what does the work.
(a) The baseurl is formed using the table name "gigya" (as descibed in the previous section on table name) 
(b) Gets dataset from "gigya" table 
(c) get row "GigyaFields" populated in "PopulateCustomData" class above, and provides this as datasource to "GigyaFieldsDataSource" rendering we created earlier.

define(\["sitecore", "/-/speak/v1/experienceprofile/DataProviderHelper.js", "/-/speak/v1/experienceprofile/CintelUtl.js"\], function (sc, providerHelper, cintelUtil) {  
    var intelPath = "/intel",  
        dataSetProperty = "dataSet";  
  
    var cidParam = "cid";  
    var intelPath = "/intel";  
  
    var getTypeValue = function (preffered, all) {  
        if (preffered.Key) {  
            return { type: preffered.Key, value: preffered.Value };  
        } else if (all.length > 0) {  
            return { type: all\[0\].Key, value: all\[0\].Value };  
        }  
  
        return null;  
    };  
  
    var app = sc.Definitions.App.extend({  
        initialized: function () {  
            var transformers = $.map(  
                \[  
                    "default"  
                \], function (tableName) {  
                    return { urlKey: intelPath + "/" + tableName + "?", headerValue: tableName };  
                });  
  
            providerHelper.setupHeaders(transformers);  
            providerHelper.addDefaultTransformerKey();  
  
            this.setupContactDetail();  
            this.setupContactGigyaDetails();  
        },  
  
        setEmail: function (textControl, email) {  
            if (email && email.indexOf("@") > -1) {  
                cintelUtil.setText(textControl, "", true);  
                textControl.viewModel.$el.html('<a href="mailto:' + email + '">' + email + '</a>');  
            } else {  
                cintelUtil.setText(textControl, email, true);  
            }  
        },  
  
        setupContactGigyaDetails: function () {  
            var contactId = cintelUtil.getQueryParam(cidParam);  
            var tableName = "gigya";  
            var baseUrl = "/sitecore/api/ao/v1/contacts/" + contactId + "/intel/"+tableName;  
  
            providerHelper.initProvider(this.ContactDetailsDataProvider,  
                tableName,  
                baseUrl,  
                this.DetailsTabMessageBar);  
  
            providerHelper.getData(this.ContactDetailsDataProvider,  
                $.proxy(function (jsonData) {  
                    var dataSetProperty = "Data";  
                    if (jsonData.data.dataSet != null && jsonData.data.dataSet.gigya.length > 0) {  
                        // Data present set value content  
                        var dataSet = jsonData.data.dataSet.gigya\[0\];  
                        this.ContactDetailsDataProvider.set(dataSetProperty, jsonData);  
                        this.GigyaFieldsDataSource.set('items', dataSet.GigyaFields);  
                    }   
                }, this));  
        },  
  
        setupContactDetail: function () {  
            var getFullAddress = function (data) {  
                var addressParts = \[  
                    data.streetLine1,  
                    data.streetLine2,  
                    data.streetLine3,  
                    data.streetLine4,  
                    data.city,  
                    data.country,  
                    data.postalCode  
                \];  
  
                addressParts = $.map(addressParts, function (val) { return val ? val : null; });  
                return addressParts.join(", ");  
            };  
  
            providerHelper.initProvider(this.ContactDetailsDataProvider, "", sc.Contact.baseUrl, this.DetailsTabMessageBar);  
            providerHelper.getData(  
                this.ContactDetailsDataProvider,  
                $.proxy(function (jsonData) {  
                    this.EmailColumnDataRepeater.viewModel.addData(jsonData.emailAddresses);  
                    this.PhoneColumnDataRepeater.viewModel.addData(jsonData.phoneNumbers);  
                    this.AddressColumnDataRepeater.viewModel.addData(jsonData.addresses);  
  
                    this.ContactDetailsDataProvider.set(dataSetProperty, jsonData);  
  
                    cintelUtil.setText(this.FirstNameValue, jsonData.firstName, false);  
                    cintelUtil.setText(this.MiddleNameValue, jsonData.middleName, false);  
                    cintelUtil.setText(this.LastNameValue, jsonData.surName, false);  
  
                    cintelUtil.setTitle(this.FirstNameValue, jsonData.firstName);  
                    cintelUtil.setTitle(this.MiddleNameValue, jsonData.middleName);  
                    cintelUtil.setTitle(this.LastNameValue, jsonData.surName);  
  
                    cintelUtil.setText(this.TitleValue, jsonData.jobTitle, false);  
  
                    cintelUtil.setText(this.GenderValue, jsonData.gender, false);  
                    cintelUtil.setText(this.BirthdayValue, jsonData.formattedBirthDate, false);  
  
                    var dataSet = this.ContactDetailsDataProvider.get(dataSetProperty);  
  
                    var email = getTypeValue(jsonData.preferredEmailAddress, dataSet.emailAddresses);  
                    if (email) {  
                        cintelUtil.setText(this.PrimeEmailType, email.type, true);  
                        this.setEmail(this.PrimeEmailValue, email.value.SmtpAddress);  
                        cintelUtil.setTitle(this.PrimeEmailValue, email.value.SmtpAddress);  
                    }  
  
                    var phone = getTypeValue(jsonData.preferredPhoneNumber, dataSet.phoneNumbers);  
                    if (phone) {  
                        cintelUtil.setText(this.PrimePhoneType, phone.type, true);  
                        cintelUtil.setText(this.PrimePhoneValue, cintelUtil.getFullTelephone(phone.value), true);  
                    }  
  
                    var address = getTypeValue(jsonData.preferredAddress, dataSet.addresses);  
                    if (address) {  
                        cintelUtil.setText(this.PrimeAddressType, address.type, true);  
                        cintelUtil.setText(this.PrimeAddressValue, getFullAddress(address.value), true);  
                    }  
  
  
                }, this)  
            );  
  
            this.EmailColumnDataRepeater.on("subAppLoaded", function (args) {  
                cintelUtil.setText(args.app.Type, args.data.Key, true);  
                this.setEmail(args.app.Value, args.data.Value.SmtpAddress);  
                cintelUtil.setTitle(args.app.Value, args.data.Value.SmtpAddress);  
            }, this);  
  
            this.PhoneColumnDataRepeater.on("subAppLoaded", function (args) {  
                cintelUtil.setText(args.app.Type, args.data.Key, true);  
                cintelUtil.setText(args.app.Value, cintelUtil.getFullTelephone(args.data.Value), true);  
            }, this);  
  
            this.AddressColumnDataRepeater.on("subAppLoaded", function (args) {  
                cintelUtil.setText(args.app.Type, args.data.Key, true);  
                cintelUtil.setText(args.app.Value, getFullAddress(args.data.Value), true);  
            }, this);  
        }  
    });  
    return app;  
});

11- Right-click on "DetailsPanel" and view the designer that opens, in this case "DetilsPanel.layout".

12- The path to "gigyatab.js" is added into the Page.Body placeholder right on top, as can be seen in 

13- You should now be all set and can view the custom facet field/values in the custom facet, in my case "gigya", as can be seen here