If you've played around with the Experience Profile in Sitecore, you may have noticed the red bar at the top of the timeline with the label Unknown Contact
. In this post I'll go into depth about this red bar--a feature in the Experience Profile timeline called eras--and how you can customize it in your Sitecore project.
Update (February 23, 2020)
All of the code in this post has been updated to work with Sitecore 9.3. See this repository on GitHub for a sample project: https://github.com/coreyasmith/sitecore-custom-timeline-eras. I've added tags to the repository for versions of the code that work with Sitecore 8.2 Update-4 and 8.2 Update-7, too.
What Is This Red Bar in the Experience Profile Timeline?
The Experience Profile timeline shows historical information about a customer's journey with your site: when the customer first visited your site, when the customer triggered goals, outcomes, or campaign events, etc. Starting with your customer's first visit to your site, you'll see a red bar at the top of the Experience Profile timeline labeled Unknown Contact
. This red bar represents the first era of your customer's engagement with your site as a new, unknown contact.
Eras represent evolutions in your customer's relationship with your brand. If your company has a rewards program, you might have eras such as New Member
, Silver Member
, Gold Member
, or Platinum Member
when your customer triggers certain outcomes in your system, such as registering as a new user or spending a certain amount of money on goods.
Timeline eras are tied directly to outcomes in Sitecore. Outcomes are a lot like goals, but typically have a monetary value associated with them. Also, unlike goals, outcomes can only be triggered programatically through code; there is no way to trigger outcomes out of the box. You can read more about outcomes on Sitecore's Outcome documentation.
Add New Eras
The first thing to note about outcomes is that not all of them create an era in the Experience Profile timeline. Sitecore comes with a handful of era-triggering outcomes out of the box: Marketing Lead
, Sales Lead
, Opportunity
, Close - Won
, Close - Lost
, and Close - Cancelled
. These can all be found in the Marketing Control Panel under the Outcomes
node at /sitecore/system/Marketing Control Panel/Outcomes
. What makes these outcomes show as eras is the fact that they are tagged with the outcome group Lead management funnel
(item ID {605C0647-A77F-4BEB-AA92-00112AF582E7}
).
If you want to add your own era-triggering outcomes out of the box, you must tag them with Lead management funnel
. If you don't, you will see an icon for your outcome in the Experience Profile timeline when it is triggered, but no red bar. If you want outcomes with different tags to show up as eras in the Experience Profile timeline, read on!
The Anonymous Era
The timeline of every contact in the Experience Profile starts off with an era titled Unknown Contact
that is tied to the contact's first interaction with your site, a.k.a., the anonymous era. This isn't actually an outcome, it's added on the fly to the timeline every time the timeline is rendered. You won't find any outcomes in the Marketing Control Panel named Unknown Contact
, and it's not registered in xDB.
What surprised me about eras in the Experience Profile timeline (and ultimately led to this post) is the fact that identifying contacts with Tracker.Current.Session.IdentifyAs(source, knownIdentifier)
doesn't create a new, Known Contact
era to end the Unknown Contact
era. After all, once a contact is identified, he/she is no longer an Unknown Contact
.
Remove the Anonymous Era
If you don't want this era to appear on your timeline you can remove it with a pipeline processor. The processor is fairly simple:
using System.Data;
using System.Linq;
using Sitecore.Cintel.Reporting;
using Sitecore.Cintel.Reporting.Contact.Journey;
using Sitecore.Cintel.Reporting.Processors;
public class RemoveAnonymousEra : ReportProcessorBase
{
public bool ShowIcon { get; set; }
public override void Process(ReportProcessorArgs args)
{
var resultTableForView = args.ResultTableForView;
RemoveAnonymousEraFromTimeline(resultTableForView);
}
private void RemoveAnonymousEraFromTimeline(DataTable resultTable)
{
var dataRows = resultTable.AsEnumerable();
var anonymousEras = dataRows.Where(r => r.Field<string>(Schema.EraText.Name) == "Unknown Contact");
foreach (var anonymousEra in anonymousEras.ToList())
{
if (ShowIcon)
{
anonymousEra.SetField(Schema.EventType.Name, "Outcome");
}
else
{
resultTable.Rows.Remove(anonymousEra);
}
}
}
}
Patch it into the ExperienceProfileContactViews
Journey
pipeline right after the Sitecore.Cintel.Reporting.Contact.Journey.Processors.PopulateEraChanges
processor:
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<group groupName="ExperienceProfileContactViews">
<pipelines>
<journey>
<processor type="Your.Assembly.RemoveAnonymousEra, Your.Assembly"
patch:after="processor[@type='Sitecore.Cintel.Reporting.Contact.Journey.Processors.PopulateEraChanges, Sitecore.Cintel']">
<showIcon>true</showIcon>
</processor>
</journey>
</pipelines>
</group>
</pipelines>
</sitecore>
</configuration>
And with that the Unknown Contact
era disappears.
If you want to remove the initial interaction icon as well, just set showIcon
to false
in the config patch above.
The Registered Member
, Silver Member
, Gold Member
, and Platinum Member
eras you see above are just outcomes I created in Sitecore tagged with the Lead management funnel
outcome group.
Rename the Anonymous Era
If the fact that the Anonymous Era is named Unknown Contact
doesn't suit your fancy, it's easy to change that through a pipeline processor, too:
using System.Data;
using System.Linq;
using Sitecore.Cintel.Reporting;
using Sitecore.Cintel.Reporting.Contact.Journey;
using Sitecore.Cintel.Reporting.Processors;
public class RenameAnonymousEra : ReportProcessorBase
{
public string AnonymousEraName { get; set; }
public override void Process(ReportProcessorArgs args)
{
var resultTableForView = args.ResultTableForView;
RenameAnonymousEraInTimeline(resultTableForView);
}
private void RenameAnonymousEraInTimeline(DataTable resultTable)
{
var dataRows = resultTable.AsEnumerable();
var anonymousEras = dataRows.Where(r => r.Field<string>(Schema.EraText.Name) == "Unknown Contact");
foreach (var anonymousEra in anonymousEras.ToList())
{
anonymousEra.SetField(Schema.EraText.Name, AnonymousEraName);
}
}
}
Patch it into the ExperienceProfileContactViews
Journey
pipeline right after the Sitecore.Cintel.Reporting.Contact.Journey.Processors.PopulateEraChanges
processor:
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<group groupName="ExperienceProfileContactViews">
<pipelines>
<journey>
<processor type="Your.Assembly.RenameAnonymousEra, Your.Assembly"
patch:after="processor[@type='Sitecore.Cintel.Reporting.Contact.Journey.Processors.PopulateEraChanges, Sitecore.Cintel']">
<anonymousEraName>Window Shopper</anonymousEraName>
</processor>
</journey>
</pipelines>
</group>
</pipelines>
</sitecore>
</configuration>
Here you can see the Unknown Contact
era renamed to Window Shopper
for this commerce-themed example:
Again, the Registered Member
, Silver Member
, Gold Member
, and Platinum Member
eras you see above are just outcomes I created in Sitecore tagged with the Lead management funnel
outcome group.
Add New Eras with Custom Tags
The Lead management funnel
outcome group might not make sense for your marketing taxonomy. In the examples above, perhaps a Rewards program
outcome group would make more sense for those outcomes. If you want outcomes with custom groups to show up as eras in the Experience Profile timeline, you'll need some custom code. Fortunately it's not hard to create a flexible solution so that any outcome can be configured to show up as an era in the timeline without being tied to a specific outcome group.
Add Show as Era
Field to Outcome Definition
Template
First you will need a new checkbox field on your Outcome Definition items to indicate whether the item should be shown as an era. Add a new section to the Outcome Definition
template (/sitecore/templates/System/Analytics/Outcome/Outcome Definition
) called Experience Profile Options
. Add a Checkbox
field to this template called Show as Era
as shown below:
Update: I normally advocate against modifying out-of-the-box templates, and when I originally wrote this blog post for Sitecore 8 I showed an approach to create this field on a separate template. However, the Trigger Outcome
submit action for Sitecore Forms introduced in Sitecore 9 is hard coded to only allow the selection of outcomes based on the out-of-the-box Outcome Definition
template. Adding this field to the Outcome Definition
template is easier than fixing that hard-coded behavior, so here we are.
Create Pipeline Processor for Custom Eras
The Experience Profile Timeline gets its data from the Journey
pipeline in the ExperienceProfileContactViews
pipeline group. To draw eras in the Experience Profile Timeline from Outcome Definitions with the Show as Era
field checked, you have to add a new processor to this pipeline:
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using Sitecore.Cintel.ContactService;
using Sitecore.Cintel.Reporting;
using Sitecore.Cintel.Reporting.Contact.Journey;
using Sitecore.Cintel.Reporting.Processors;
using Sitecore.Data.Fields;
using Sitecore.Marketing.Definitions;
using Sitecore.Marketing.Definitions.Outcomes.Model;
using Sitecore.XConnect;
using Sitecore.XConnect.Client;
using Sitecore.XConnect.Client.Configuration;
public class PopulateCustomEraChanges : ReportProcessorBase
{
private static readonly ID ShowAsEraFieldId = new ID("{Your-Show-As-Era-Field-ID-Here}");
public override void Process(ReportProcessorArgs args)
{
var resultTableForView = args.ResultTableForView;
PopulateWithEraChanges(args.ReportParameters.ContactId, resultTableForView);
}
private void PopulateWithEraChanges(Guid contactId, DataTable resultTable)
{
var changingOutcomesFor = GetEraChangingOutcomesFor(contactId);
foreach (var dataRow in resultTable.AsEnumerable())
{
var timeLineEventId = dataRow.Field<Guid?>(Schema.TimelineEventId.Name);
if (!timeLineEventId.HasValue) continue;
var contactOutcome = changingOutcomesFor.SingleOrDefault(o => o.Id == timeLineEventId.Value);
if (contactOutcome == null) continue;
var definition = OutcomeDefinitionManager.Get(contactOutcome.DefinitionId, CurrentCultureInfo);
ConvertToEraChangeEvent(dataRow, definition);
}
}
protected virtual IReadOnlyCollection<Outcome> GetEraChangingOutcomesFor(Guid contactId)
{
var allOutcomeDefinitions = OutcomeDefinitionManager.GetAll(CultureInfo.InvariantCulture);
var allEraChangingOutcomes = allOutcomeDefinitions.Where(IsCustomEraChangingOutcome);
var contact = GetContact(contactId);
var contactOutcomes = contact.Interactions.SelectMany(i => i.Events.OfType<Outcome>());
var eraChangingOutcomes = contactOutcomes.Where(co => allEraChangingOutcomes.Any(o => o.Data.Id == co.DefinitionId));
return eraChangingOutcomes.ToList();
}
protected virtual Contact GetContact(Guid contactId)
{
using (var client = SitecoreXConnectClientConfiguration.GetClient())
{
var contactReference = new ContactReference(contactId);
var contact = client.Get(contactReference, new ContactExpandOptions(Array.Empty<string>())
{
Interactions = new RelatedInteractionsExpandOptions
{
StartDateTime = DateTime.MinValue,
Limit = int.MaxValue
}
});
if (contact == null) throw new ContactNotFoundException($"No Contact with id [{contactId}] found.");
return contact;
}
}
protected virtual bool IsCustomEraChangingOutcome(DefinitionResult<IOutcomeDefinition> outcomeDefinition)
{
var definitionItem = GetItemFromCurrentContext(outcomeDefinition.Data.Id);
var showAsEraField = (CheckboxField)definitionItem.Fields[ShowAsEraFieldId];
var isCustomEraChangingOutcome = showAsEraField?.Checked ?? false;
return isCustomEraChangingOutcome;
}
private static void ConvertToEraChangeEvent(DataRow outcomeRow, IDefinition outcomeDefinition)
{
outcomeRow.SetField(Schema.EventType.Name, Schema.TimelineEventTypes.EraChange);
outcomeRow.SetField(Schema.EraText.Name, outcomeDefinition.Name);
}
}
For this code to work, you'll need to make sure that you have the following NuGet packages added to your project through Sitecore's NuGet feed):
Sitecore.Cintel
Sitecore.Kernel
Sitecore.Mvc.Analytics
This processor is largely hacked together from the built-in Sitecore.Cintel.Reporting.Contact.Journey.Processors.PopulateEraChanges
processor since all of its interesting methods are private
. It finds all of the Outcome Definitions in the Marketing Control panel that have the custom Show as Era
field checked, sees which of those the Experience Profile contact has triggered, and returns them as era-change events on the Experience Profile timeline.
Patch this processor right after the Sitecore.Cintel.Reporting.Contact.Journey.Processors.PopulateEraChanges
processor in the ExperienceProfileContactViews
Journey
pipeline:
<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<group groupName="ExperienceProfileContactViews">
<pipelines>
<journey>
<processor type="Your.Assembly.PopulateCustomEraChanges, Your.Assembly"
patch:after="processor[@type='Sitecore.Cintel.Reporting.Contact.Journey.Processors.PopulateEraChanges, Sitecore.Cintel']" />
</journey>
</pipelines>
</group>
</pipelines>
</sitecore>
</configuration>
Create Some Outcome Definitions
With your custom field added, you can now create some Outcome Definitions in the Marketing Control Panel that will show up as eras on the Experience Profile timeline. Navigate to /sitecore/system/Marketing Control Panel/Outcomes
, create an Outcome Definition, tag it with any Outcome Group, and check Show as Era
as I have on the superhero-themed Outcome Definition below:
Now when this outcome is triggered it will show up in the Experience Profile timeline as an era:
Miscellany
Here are a few observations I made while working on this blog post:
- Era-changing outcomes have a larger icon in the Experience Profile timeline than regular outcomes.
- You can change the icons of your outcomes in the Experience Profile timeline by adding an image to the
Image
field of your Outcome Definition items. - You don't need to publish your Outcome Definition items to see changes to the names or images in the Experience Profile timeline--those changes will be reflected immediately after saving the item.
- If you have outcomes that should only be able to be triggered once then make sure that you check the
Ignore Additional Registrations
field on your Outcome Definition items. Era-changing outcomes are good candidates for this. - There is a bug with the Experience Profile timeline where it fails to render if the contact has multiple events/outcomes registered at the same time. Reach out to Sitecore Support about this patch for your instance to avoid that bug: https://github.com/SitecoreSupport/Sitecore.Support.126998.134727.
Let me know your thoughts in the comments.