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.

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.

Rewards Program Eras

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}).

Built-In Marketing Lead Outcome

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.Identify(identifier) does not 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.

Removed Unknown Contact Era

If you want to remove the initial interaction icon as well, just set showIcon to false in the config patch above.

Removed Unknown Contact Icon

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:

Window Shopper Anonymous Era

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 tags 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.

Create a Custom Outcome Definition Template

First you will need a new checkbox field on your Outcome Definition items to signal whether the item should be shown as an era. You could go and add a new checkbox field directly to the Outcome Definition template, but for maintainability, I strongly urge against modifying out-of-the-box Sitecore templates. Instead, create a new template that inherits from the Outcome Definition template (/sitecore/templates/System/Analytics/Outcome/Outcome Definition) and add a new field to the Data section as I have below:

Custom Outcome Definition Template

Choose Applications/32x32/checkbox.png as the icon for your template to match the out-of-the-box Outcome Definition template icon.

Additionally, you'll want to create a new Insert Option Rule so that content authors will only be able to use this new template to create outcomes, and not the out-of-the-box Outcome Definition template. Navigate to /sitecore/system/Settings/Rules/Insert Options/Rules and create a new Insert Option Rule as I have below:

Custom Outcome Definition Template Insert Option Rule

Select the following items for this rule:

  • Outcomes: /sitecore/system/Marketing Control Panel/Outcomes
  • Folder: /sitecore/templates/Common/Folder
  • Outcome Definition: /sitecore/templates/System/Analytics/Outcome/Outcome Definition

Important: You must make sure that your insert option rule is sorted last in the Insert Options Rules folder. This is so that your rule overwrites the built-in Outcome Definition insert option rule.

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 CustomTimelineEra;  
using Sitecore;  
using Sitecore.Analytics.Outcome.Model;  
using Sitecore.Cintel.Reporting;  
using Sitecore.Cintel.Reporting.Contact.Journey;  
using Sitecore.Cintel.Reporting.Processors;  
using Sitecore.Common;  
using Sitecore.Data;  
using Sitecore.Data.Fields;  
using Sitecore.Marketing.Definitions;  
using Sitecore.Marketing.Definitions.Outcomes.Model;

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.ToID());
      if (contactOutcome == null) continue;

      var outcomeDefinition = OutcomeDefinitionManager.Get(contactOutcome.DefinitionId, CurrentCultureInfo);
      ConvertToEraChangeEvent(dataRow, outcomeDefinition);
    }
  }

  protected virtual List<ContactOutcome> GetEraChangingOutcomesFor(Guid contactId)
  {
    var allOutcomeDefinitions = OutcomeDefinitionManager.GetAll(CultureInfo.InvariantCulture);
    var allEraChangingOutcomes = allOutcomeDefinitions.Where(IsCustomEraChangingOutcome).ToList();

    var contactOutcomes = OutcomeManager.GetForEntity<ContactOutcome>(contactId.ToID());
    var contactEraChangingOutcomes = contactOutcomes.Where(oc => allEraChangingOutcomes.Any(od => od.Data.Id == oc.DefinitionId));
    return contactEraChangingOutcomes.ToList();
  }

  protected virtual bool IsCustomEraChangingOutcome(DefinitionResult<IOutcomeDefinition> outcomeDefinition)
  {
    var outcomeDefinitionItem = Context.Database.GetItem(outcomeDefinition.Data.Id);
    var showAsEraField = (CheckboxField)outcomeDefinitionItem.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 (long) list of assemblies added as references to your project (preferably through Sitecore's NuGet feed):

  • Sitecore.Analytics
  • Sitecore.Analytics.Model
  • Sitecore.Analytics.Outcome
  • Sitecore.Cintel
  • Sitecore.Kernel
  • Sitecore.Marketing
  • Sitecore.Marketing.Core
  • Sitecore.Marketing.Taxonomy

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 template and insert option rule created, 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 from your new template, tag it with any Outcome Group, and check Show as Era as I have on the superhero-themed Outcome Definition below:

Superhero-Themed Outcome Definition

Now when this outcome is triggered it will show up in the Experience Profile Timeline as an era:

Bruce Wayne's Experience Profile Timeline

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.

All of the code for this blog post can be found on GitHub here: https://github.com/coreyasmith/Sitecore.CustomTimelineEra.

Let me know your thoughts in the comments.