Display the Context of a Sitecore Goal in the Experience Profile

For a while now I’ve been writing about collecting and displaying data unique to a client in Sitecore’s Experience Profile. What went hand in hand with that is the use of Sitecore Goals and Outcomes – giving a business value, sometimes monetary, to actions performed on the site. The Experience Profile currently shows the goals for each Contact.

However, beyond the goal being registered we don’t know the context in which the goal was met. Wouldn’t it be great if we knew WHAT was added to the basket when the ‘Add to Basket’ goal was registered?

In my post extending xDB to show custom data I described how we can get that contextual data into xDB. I.e. a JavaScript function to initiate when an action is made by a User. Retrieving the contextual information from the markup of the page. Finally passing the goal and goal context to a MVC Controller to register it.

For example, a directory listing for a business’ employees. A goal is registered when the User clicks the ‘Call Now’ or ‘Email Now’ button to communicate with that member of staff. We will register the ‘Called Employee’ or ‘Emailed Employee’ goal but we can also include which employee contacted and the phone number or email address used.

But the Experience Profile doesn’t show this valuable contextual information even though its in xDB. So let’s do something about that.

Extending the Goals ExperienceProfileContactViews Pipeline

Back to the old ExperienceProfileContactViews Pipeline Groups. This time, we’re going to extend the Goals section to include two new pipelines. First, a pipeline to add an additional column to the DataTable to hold the Goal’s Context. Secondly, a pipeline to fill that column with data retrieved from xDB via a new DataSourceQuery, which we’ll get to shortly.


<sitecore>
<pipelines>
<group groupName="ExperienceProfileContactViews">
<pipelines>
<goals>
<processor patch:after="*[@type='Sitecore.Cintel.Reporting.Contact.Goal.Processors.ConstructGoalsDataTable, Sitecore.Cintel']"
type="JonathanRobbins.DisplayGoalContext.Reporting.Contact.Goals.Processors.AddGoalDescriptionColumn, JonathanRobbins.DisplayGoalContext" />
<processor patch:after="*[@type='Sitecore.Cintel.Reporting.Contact.Goal.Processors.PopulateGoalsWithXdbData, Sitecore.Cintel']"
type="JonathanRobbins.DisplayGoalContext.Reporting.Contact.Goals.Processors.FillGoalDescription, JonathanRobbins.DisplayGoalContext" />
</goals>
</pipelines>
</group>
</pipelines>
</sitecore>

With me so far? Good.

Adding the column is as simple as below but needs to occur once the GoalsDataTable has been constructed by the appropriate pipeline.


public class AddGoalDescriptionColumn : ReportProcessorBase
{
public override void Process(ReportProcessorArgs args)
{
if (args.ResultTableForView == null)
return;
args.ResultTableForView.Columns.Add(Schemas.GoalDescription.ToColumn());
}
}

Entering the context of the goal into that column is a little more work. Pulling apart the data in the QueryResult to get the goal context and entering it into our new column. This needs to occur after the existing pipeline which populates the goals with data from xDB.


public class FillGoalDescription : ReportProcessorBase
{
public override void Process(ReportProcessorArgs args)
{
DataTable resultTableForView = args.ResultTableForView;
Assert.IsNotNull(resultTableForView, "Result table for {0} could not be found.", new object[] { args.ReportParameters.ViewName });
int i = 0;
foreach (DataRow row in resultTableForView.AsEnumerable())
{
var goalData = args.QueryResult.Rows[i].ItemArray[4];
if (goalData != null)
{
row[Schemas.GoalDescription.Name] = goalData;
}
i++;
}
}
}

The schema which files both reference is as follows


namespace JonathanRobbins.DisplayGoalContext.Reporting.Contact.Goals
{
public static class Schemas
{
public static ViewField<string> GoalDescription = new ViewField<string>("GoalDescription");
}
}

view raw

Schemas.cs

hosted with ❤ by GitHub

Retrieving Goal information from xDB

As you may know, the Goal Type is a PageEvent. PageEvents are stored in xDB against a Contact’s Interaction, not the Contact themselves. Which makes sense architecturally speaking. A Contact interacts with the site, that interaction includes a number of page views, a page view may result in a page event.

When registering a goal I use the Description property to hold the context of the goal. In this example, it would be the name of the Employee the user chose to contact and medium of the contact; phone number or email address. This is done by the following;


public class PageEventController : Controller
{
[System.Web.Mvc.HttpPost]
public JsonResult RegisterGoal(string goalId, string goalDescription)
{
Item eventItem = Sitecore.Context.Database.GetItem(goalId);
var goal = new PageEventItem(eventItem);
if (!Tracker.IsActive)
Tracker.StartTracking();
Sitecore.Analytics.Model.PageEventData eventData = Tracker.Current.CurrentPage.Register(goal);
eventData.Data = goal["Description"] + " " + goalDescription;
Tracker.Current.Interaction.AcceptModifications();
return Json(new PageEventRequestResult()
{
Success = true,
Message = "Successfully registered goal",
});
}
}

This Goal Description ends up in the Data property on the PageEventData type which is written to xDB (so we don’t have to worry about writing something custom there). So in order to retrieve it for our new column we need to query xDB. To do that we need to replace the existing Sitecore.Cintel.Reporting.ReportingServerDatasource.Goals.GetGoals type with our own.


<sitecore>
<pipelines>
<group groupName="ExperienceProfileContactDataSourceQueries">
<pipelines>
<goals-query>
<processor patch:instead="*[@type='Sitecore.Cintel.Reporting.ReportingServerDatasource.Goals.GetGoals, Sitecore.Cintel']"
type="JonathanRobbins.DisplayGoalContext.Reporting.ReportingServerDatasource.Goals.GetGoals, JonathanRobbins.DisplayGoalContext" />
</goals-query>
</pipelines>
</group>
</pipelines>
</sitecore>

Similar to the existing GetGoals type we are replacing, we are querying the Interactions Collection in xDB for entries matching the Contact’s Id we are currently viewing in the Experience Profile. We are asserting that at least one PageEvent exists in the Page.PageEvents property so we don’t get null PageEvents. Our query requests which Fields we want from the entry specifying the Pages_PageEvents_Data property which holds our goal context.


public class GetGoals : ReportProcessorBase
{
private readonly QueryBuilder goalsQueryBuilder = new QueryBuilder()
{
collectionName = "Interactions",
QueryParms =
{
{
"ContactId", "@contactId"
},
{
"Pages.PageEvents.0", "{$exists:1}"
}
},
Fields =
{
"_id",
"ContactId",
"Pages_PageEvents_PageEventDefinitionId",
"Pages_PageEvents_DateTime",
"Pages_PageEvents_Data",
"Pages_Url_Path",
"Pages_Url_QueryString",
"Pages_PageEvents_Value",
"Pages_Item__id"
}
};
public override void Process(ReportProcessorArgs args)
{
DataTable goalsData = this.GetGoalsData(args.ReportParameters.ContactId);
args.QueryResult = goalsData;
}
private DataTable GetGoalsData(Guid contactId)
{
ReportDataProvider reportDataProvider = this.GetReportDataProvider();
Assert.IsNotNull((object)reportDataProvider, "provider should not be null");
return reportDataProvider.GetData("collection", new ReportDataQuery(this.goalsQueryBuilder.Build())
{
Parameters =
{
{
"@contactId", (object) contactId
}
}
}, new CachingPolicy()
{
NoCache = true
}).GetDataTable();
}
}

view raw

GetGoals.cs

hosted with ❤ by GitHub

Almost there

The Experience Profile being built with SPEAK means to finish this off we need to do a bit. Fortunately, it is a very small bit. We simply need to add a new Item based on the ColumnField template in the Goals ListControl so that our new column displays in the list of Goals. Create this item under the path /sitecore/client/Applications/ExperienceProfile/Contact/PageSettings/Tabs/Activity/Activity Subtabs/Goals/GoalsPanel/Goals entering a sensible HeaderText and a DataField value that matches our schema name.

The result of our hard work;

Sitecore Goals Context

And that’s it!

A good effective improvement to the Goals area of the Sitecore Experience Profile. Give your Content Editors the what, why and how for when the User achieved the goal.

If you like this but don’t fancy doing work I’ll be submitting this as a Sitecore Marketplace Module shortly so you can just take it from there. I will update this post soon as its available for all!

3 thoughts on “Display the Context of a Sitecore Goal in the Experience Profile

  1. Hey Jonathan, great post. If you were to report on where the goal was triggered from (e.g. mobile, app, desktop) while using webapi to register goals. Would it be the same/similar procedure?

    Like

  2. Great blog post! I have once extended Pave Events with custom data but I wanted more than a string to show in a report. I wanted to store a JSON that I could later interact with and get something out of. It worked but I wish xDB data APIs weren’t “stringly” typed. Would nice if it accepted a nested document in the Data field, and not just a string. I blogged about it last year here – http://jockstothecore.com/xdb-tracking-the-untrackable-part-2/

    Liked by 1 person

    • I agree, it would be great if we can something more complex than strings. Facets in xDB are the closest we can get to storing something we can later interact with. But with the way xDB is heading, I don’t think it will be long until we get what we want and more.

      Like

Leave a comment