Walkthrough: Reporting on a new dimension
Sitecore has a number of dimensions that it reports on. If you want to report on a dimension that Sitecore does not report on, you can create your own dimension and collect metrics for it.
This topic describes how you implement dimensions, also known as flexible dimensions, in Sitecore 9 and later.
This walkthrough describes how to:
-
Create classes for dimensions and metrics - you create classes to represent the dimension and do the metrics calculations.
-
Update the reporting database - you update the reporting database to store your dimension data there.
-
Create items for dimension and metrics - you create Sitecore items that represent the dimension and its metrics.
-
Configure the new dimension - you configure the dimension to make Sitecore use it.
When you have created and configured the new dimension, you can add charts that display the data.
Create classes for dimensions and metrics
The following procedures show how you create the classes where you implement your new dimensions and calculate metrics.
Add references
Add references to these libraries in your Visual Studio solution:
Sitecore.Analytics.Aggregation
Sitecore.ExperienceAnalytics.Aggregation
Sitecore.XConnect
Sitecore.Kernel
Define a model
To create a model for the dimension and the metrics:
-
Create a class that extends the
DictionaryValue
class and theIMergeableMetric<TableName>
interface. The naming convention for these classes is <YourDimension>Metrics, for examplePostCodeMetrics
,OutcomeMetrics
, and so on. -
Define properties for your metrics, for example
Visits
,MonetaryValue
, and so on. -
Implement the
MergeWith
method. In this method you specify how data that is calculated for a new interaction is merged into the already existing calculated data.
The following example shows the implementation of a dimension called Browser
that collects engagement values and bounces of a visit:
class BrowserMetrics : DictionaryValue, IMergeableMetric<BrowserMetrics>
{
public int Visits { get; set; }
public int EngagementValue { get; set; }
public int Bounces { get; set; }
public T MergeWith<T>(T other) where T : BrowserMetrics, new()
{
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
return new T
{
Visits = Visits + other.Visits,
Bounces = Bounces + other.Bounces,
EngagementValue = other.EngagementValue
};
}
}
You use the names of the class and the metrics properties in a later task where you define the database schema.
Create a group resolver
You must define a strategy for resolving the keys used to group the dimensions, for example URL, Goal, or Outcome. You do this by implementing a group resolver.
To create a group resolver:
-
Create a new class that extends the
IGroupResolver<T>
interface, whereT
is the type object you want to use as a key when you group records for your dimension. -
Implement the
MeasureGroupOccurrences
method.
In the following example you measure metrics for Browser and, therefore, you use the BrowserData
type to resolve groups:
class BrowserResolver : IGroupResolver<BrowserData>
{
public IEnumerable<VisitGroupMeasurement<BrowserData>> MeasureGroupOccurrences(IInteractionAggregationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var browserData = context.Interaction.WebVisit()?.Browser;
var occurrences = new List<VisitGroupMeasurement<BrowserData>>();
if (browserData != null)
{
var serializedBrowserDataKey = JsonConvert.SerializeObject(browserData, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
occurrences.Add(new VisitGroupMeasurement<BrowserData>(new VisitGroup(serializedBrowserDataKey), new[] { browserData }));
return occurrences;
}
return occurrences;
}
}
The BrowserData
JSON might be too large to be used as a key in the metric, and it has a lot of unwanted information. You must use a key resolver to use the correct information as the key.
To create a key resolver:
-
Create a class that extends the
IVisitGroupKeyResolver<String>
interface:RequestResponseshellclass BrowserKeyResolver: IVisitGroupKeyResolver<string> { public IEnumerable<string> GetKeys(IVisitGroup visitGroup) { if (visitGroup == null) { throw new ArgumentNullException(nameof(visitGroup)); } var browserDataKey = JsonConvert.DeserializeObject<BrowserData>(visitGroup.Id, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }); return new List<string> { browserDataKey.BrowserMinorName} }; } }
NoteIf you use
Guid
orstring
for theVisitGroup
ID,BrowserKeyResolver
becomes optional.
Create a fact calculator
You must create a fact calculator. This is the class that calculates the values of individual metrics defined in your model for this dimension, for example number of page views, bounce rate, and so on.
To create a fact calculator:
-
Create a class the extends the
IFactCalculator<out TValue, TGroup>
interface, whereTValue
is the type of the first class you created andTGroup
is the group type in theIGroupResolver
class you created. -
Implement the
CalculateFactsForGroup
method. This method calculates the individual metrics for the specific groups.
The following example measures the total engagement value, the number of visits, and the bounces that happened during the visit:
class BrowserFactCalculator : BaseMetricCalculator, IFactCalculator<BrowserMetrics, BrowserData>
{
public BrowserMetrics CalculateFactsForGroup(VisitGroupMeasurement<BrowserData> groupMeasurement, IInteractionAggregationContext context)
{
var calculatedInteraction = CalculateInteraction(context);
return new BrowserMetrics
{
Bounces = calculatedInteraction.Bounces,
EngagementValue = calculatedInteraction.EngagementValue,
Visits = 1
};
}
}
Create a parent key resolver
This step is optional. If you have a series of dimensions that are hierarchical in nature, for example outcome groups that contain many outcomes, you can implement a parent key resolver to represent the relationship in the hierarchy.
To create a parent key resolver:
-
Create a class that extends either the
IVisitGroupKeyResolver<GUID>
or theIVisitGroupKeyResolver<String>
interface.
For example, you want to be able to drill down from an browser group to a specific version of the browser. To do this, you must resolve the browser group and its associated group keys by implementing the GetKeys
method. This method returns the GUID of the browser group parent item:
class BrowserParentKeyResolver : IVisitGroupKeyResolver<string>
{
public IEnumerable<string> GetKeys(IVisitGroup visitGroup)
{
if (visitGroup == null)
{
throw new ArgumentNullException(nameof(visitGroup));
}
var browserDataKey = JsonConvert.DeserializeObject<BrowserData>(visitGroup.Id, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
return new List<string>
{
browserDataKey.BrowserMajorName
};
}
}
Create an interaction filter
This step is optional. You can implement interaction filters for your dimension. You use an interaction filter to ignore data related to an interaction, based on events, goals, campaigns, channels, contacts, or downloads. For example, if you do not want a certain page on your website to appear as part of your reports, you create a filter that specifies that the page should be ignored.
To create an interaction filter:
-
Create a class that extends the
IInteractionFilter
interface and implements theKeepInteraction
method.
In the following example you restrict processing so that only data for interactions that have events of a specific type based on their definition GUID are calculated:
class EventFilter : IInteractionFilter
{
public bool KeepInteraction(Interaction interaction)
{
if (interaction.Events.Any(data => ReservedIds.Contains(data.DefinitionId)))
{
return true;
}
return false;
}
internal static readonly Guid[] ReservedIds =
{
// list of guids
};
}
Update the reporting database
You must update the reporting database to store data for the new dimension. You create a table to store results, you create a type to represent the data, and you create a stored procedure to manipulate the data stored in the table.
Create a table
Create a table in SQL with a name following this convention: Fact_
YourDimensionNameMetrics
where YourDimensionName is the dimension name you used when you created your class.
When you create the table several columns are mandatory and required by Sitecore:
-
SegmentRecordId
-
SegmentId
-
Date
-
SiteNameId
-
DimensionKeyId
You must create a column to represent each metric property defined on your model class and the names must match. This is an example of a script to do this:
CREATE TABLE [dbo].[Fact_BrowserMetrics](
-- Mandatory Columns
[SegmentRecordId] [bigint] NOT NULL,
[SegmentId] [uniqueidentifier] NOT NULL,
[Date] [smalldatetime] NOT NULL,
[SiteNameId] [int] NOT NULL,
[DimensionKeyId] [bigint] NOT NULL,
-- Dimension specific metric columns
[Visits] [int] NOT NULL,
[EngagementValue] [int] NOT NULL,
[Bounces] [int] NOT NULL,
CONSTRAINT [PK_Fact_BrowserMetrics_1] PRIMARY KEY CLUSTERED
(
[SegmentRecordId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
Create a type and a stored procedure
You use a type and a stored procedure to manipulate the data in your table. The type must match the structure of the table exactly. This is an example:
CREATE TYPE [dbo].[BrowserMetrics_Type] AS TABLE(
-- Mandatory Columns
[SegmentRecordId] [bigint] NOT NULL,
[SegmentId] [uniqueidentifier] NOT NULL,
[Date] [smalldatetime] NOT NULL,
[SiteNameId] [int] NOT NULL,
[DimensionKeyId] [bigint] NOT NULL,
-- Dimension specific metric columns
[Visits] [int] NOT NULL,
[EngagementValue] [int] NOT NULL,
[Bounces] [int] NOT NULL,
PRIMARY KEY CLUSTERED
(
[SegmentRecordId] ASC
)WITH (IGNORE_DUP_KEY = OFF)
)
The stored procedure must create a record in the table if this does not exist, or add the incoming values to an existing record, and capture any errors. This is an example:
CREATE PROCEDURE [dbo].[Add_BrowserMetrics_Tvp]
@table [dbo].[BrowserMetrics_Type] READONLY
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
MERGE [Fact_BrowserMetrics] AS Target USING @table AS Source
ON
Target.[SegmentRecordId] = Source.[SegmentRecordId]
WHEN MATCHED THEN
UPDATE SET
Target.[Visits] = (Target.[Visits] + Source.[Visits]),
Target.[EngagementValue] = (Target.[EngagementValue] + Source.[EngagementValue]),
Target.[Bounces] = (Target.[Bounces] + Source.[Bounces]),
WHEN NOT MATCHED THEN
INSERT (
[SegmentRecordId],
[SegmentId],
[Date],
[SiteNameId],
[DimensionKeyId],
[FilterId],
[Visits],
[EngagementValue],
[Bounces]
)
VALUES (
Source.[SegmentRecordId],
Source.[SegmentId],
Source.[Date],
Source.[SiteNameId],
Source.[DimensionKeyId],
Source.[FilterId],
Source.[Visits],
Source.[EngagementValue],
Source.[Bounces]
);
END TRY
BEGIN CATCH
DECLARE @error_number INTEGER = ERROR_NUMBER();
DECLARE @error_severity INTEGER = ERROR_SEVERITY();
DECLARE @error_state INTEGER = ERROR_STATE();
DECLARE @error_message NVARCHAR(4000) = ERROR_MESSAGE();
DECLARE @error_procedure SYSNAME = ERROR_PROCEDURE();
DECLARE @error_line INTEGER = ERROR_LINE();
RAISERROR( N'T-SQL ERROR %d, SEVERITY %d, STATE %d, PROCEDURE %s, LINE %d, MESSAGE: %s', @error_severity, 1, @error_number, @error_severity, @error_state, @error_procedure, @error_line, @error_message ) WITH NOWAIT;
END CATCH;
END
Create items for dimensions and metrics
The next steps are to create Sitecore definition items that represent your dimension and metrics. You use these items when you create new reports.
Create the metrics items
To create a metrics item:
-
In the Content Editor, navigate to the FlexibleMetrics folder in the Core database (
sitecore
/Client/Applications/ExperienceAnalytics/Common/System/ChartFields/FlexibleMetrics
). -
For each metric associated with your new dimension, in the Home tab, in the Insert group, select, insert, and name a MetricChartField. A field can already exist because metrics can be shared across multiple dimensions. Following the example in the previous sections, you need to create a definition item for the
EngagementValue
custom metric. -
Select the new item and specify values in these fields:
-
In the Appearance section, in the HeaderText field, enter a descriptive name for the new metric.
-
In the DataBinding section, in the DataField field enter the name (in camelCase) of the corresponding column in the table you have created.
-
-
Save.
Create and configure the dimension item
To create and configure the dimension item:
-
In the Content Editor, navigate to the Dimensions folder in the Marketing Control Panel in the Master database (Marketing Control Panel/Experience Analytics/Dimensions) and select the subfolder to store your flexible dimension item in. You can choose Pages, Visits, or create your own subfolder.
-
On the Home tab, in the Insert group, select, insert, and name a FlexibleDimension item.
-
Select the new item and specify values in these fields:
-
In the Data section, in the Metric Type field, enter the name of the class you created (for example
BrowserMetrics
). -
In the Name field, enter a descriptive name.
-
In the Metrics field, select all metrics you want to add to the dimension.
-
Save.
-
Create a segment item
Experience Analytics uses segments to filter data into smaller sets when aggregating. You must specify at least one segment before any data is aggregated. You normally specify an all interactions/visits segment for this purpose. Create a custom report filter and segment has more information.
To create a segment:
-
In the Content Editor, navigate to /sitecore/System/Marketing Control Panel/Experience Analytics/Dimensions, and select the dimension you created.
-
In the Home tab, in the Insert group, select, insert, and name a Segment item.
-
In the Review tab, in the Workflow group, click Deploy. By default, this starts data collection for the segment 30 minutes after deployment.
Do not change a segment after you have deployed it. If you do, it can cause data inconsistencies.
Configure the new dimension
You must specify the new dimension in the Sitecore configuration to invoke it during aggregation.
To configure the new dimension:
-
Create a patch file similar to this:
RequestResponsexml<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> <sitecore> <experienceAnalytics> <aggregation> <flexibleDimensions> <!--Add references to your new dimension here --> <dimension id="Guid of your new dimension item" type="Sitecore.ExperienceAnalytics.Aggregation.FlexibleMetrics.Framework.FlexibleDimension`2[[Metrics Class fully qualified name, metrics class assembly name], [Fully qualified name of Group, Assembly name of Group]], Sitecore.ExperienceAnalytics.Aggregation"> <param type="GroupResolver fully qualified name, GroupResolver assembly name" /> <param type="FactCalculator fully qualified name, FactCalculator assembly name"/> </dimension> </flexibleDimensions> </aggregation> </experienceAnalytics> </sitecore> </configuration>
-
Optionally, specify parent key resolvers and filters by adding additional
param
elements to your dimension:RequestResponseshell<dimension …> ... <param type="Sitecore.ExperienceAnalytics.Aggregation.FlexibleMetrics.Framework.KeyComposition.HierarchicalKeyComposer`1[[System.Guid]], Sitecore.ExperienceAnalytics.Aggregation"> <ParentVisitGroupKeyResolvers hint="list"> <ParentVisitGroupKeyResolver type="ParentKeyResolver fully qualified name, assembly name" /> </ParentVisitGroupKeyResolvers> </param> <InteractionFilters hint ="list"> <InteractionFilter type="Filter fully qualified name, assembly name" /> </InteractionFilters> </dimension>