Skip to content
Draft
2 changes: 1 addition & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<PackageVersion Include="log4net" Version="3.2.0" />
<PackageVersion Include="Microsoft.AppCenter.Analytics" Version="5.0.7" />
<PackageVersion Include="Microsoft.AppCenter.Crashes" Version="5.0.7" />
<PackageVersion Include="Microsoft.ApplicationInsights" Version="2.23.0" />
<PackageVersion Include="Microsoft.ApplicationInsights" Version="3.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.3" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,30 @@ namespace Splat.ApplicationInsights;
/// should be used on a single thread.</remarks>
public sealed class ApplicationInsightsFeatureUsageTrackingSession : IFeatureUsageTrackingSession<Guid>
{
/// <summary>
/// The Application Insights telemetry client used to send events and exceptions.
/// </summary>
private readonly TelemetryClient _telemetryClient;

/// <summary>
/// Initializes a new instance of the <see cref="ApplicationInsightsFeatureUsageTrackingSession"/> class.
/// </summary>
/// <param name="featureName">The name of the feature.</param>
/// <param name="telemetryClient">The Application Insights telemetry client instance to use.</param>
/// <param name="featureName">The name of the feature being tracked.</param>
/// <param name="telemetryClient">The Application Insights telemetry client instance to use for sending telemetry data.</param>
public ApplicationInsightsFeatureUsageTrackingSession(
string featureName,
TelemetryClient telemetryClient)
: this(featureName, Guid.Empty, telemetryClient)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ApplicationInsightsFeatureUsageTrackingSession"/> class
/// with a parent reference for sub-feature tracking.
/// </summary>
/// <param name="featureName">The name of the feature being tracked.</param>
/// <param name="parentReference">The unique identifier of the parent feature session, or <see cref="Guid.Empty"/> if this is a top-level session.</param>
/// <param name="telemetryClient">The Application Insights telemetry client instance to use for sending telemetry data.</param>
internal ApplicationInsightsFeatureUsageTrackingSession(
string featureName,
Guid parentReference,
Expand Down Expand Up @@ -75,6 +85,10 @@ public void OnException(Exception exception)
_telemetryClient.TrackException(telemetry);
}

/// <summary>
/// Tracks a feature usage event with the specified event name to Application Insights.
/// </summary>
/// <param name="eventName">The name of the event to track (e.g., "Feature Usage Start" or "Feature Usage End").</param>
private void TrackEvent(string eventName)
{
var eventTelemetry = new EventTelemetry(eventName);
Expand All @@ -83,6 +97,11 @@ private void TrackEvent(string eventName)
_telemetryClient.TrackEvent(eventTelemetry);
}

/// <summary>
/// Populates the standard feature tracking properties on a telemetry item.
/// </summary>
/// <typeparam name="TTelemetry">The type of telemetry item that supports custom properties.</typeparam>
/// <param name="eventTelemetry">The telemetry item to populate with feature name, reference, and optional parent reference properties.</param>
private void PrepareEventData<TTelemetry>(TTelemetry eventTelemetry)
where TTelemetry : ISupportProperties
{
Expand Down
23 changes: 7 additions & 16 deletions src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// Copyright (c) 2026 ReactiveUI. All rights reserved.
// Copyright (c) 2026 ReactiveUI. All rights reserved.
// Licensed to ReactiveUI under one or more agreements.
// ReactiveUI licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;

using Splat.ApplicationPerformanceMonitoring;

Expand All @@ -15,25 +14,17 @@ namespace Splat;
/// </summary>
/// <remarks>This class is typically used to integrate view navigation tracking into applications that utilize
/// Application Insights for telemetry. It implements the IViewTracking interface to standardize view tracking across
/// different telemetry providers.</remarks>
/// different telemetry providers. In Application Insights v3, page views are tracked as custom events since the
/// PageViewTelemetry type has been removed.</remarks>
/// <param name="telemetryClient">The Application Insights telemetry client used to send page view tracking data. Cannot be null.</param>
public sealed class ApplicationInsightsViewTracking(TelemetryClient telemetryClient) : IViewTracking
{
/// <summary>
/// Track a view navigation using just a name.
/// </summary>
/// <param name="name">Name of the view.</param>
public void OnViewNavigation(string name) => telemetryClient.TrackPageView(name);

/// <summary>
/// Track a View Navigation with Extended Data.
/// </summary>
/// <param name="telemetry">Telemetry data.</param>
public void OnViewNavigation(PageViewTelemetry telemetry)
{
_ = GetPageViewTelemetry();
telemetryClient.TrackPageView(telemetry);
}

internal static PageViewTelemetry GetPageViewTelemetry() => new();
public void OnViewNavigation(string name) =>
telemetryClient.TrackEvent(
"PageView",
new Dictionary<string, string> { ["Name"] = name });
}
Comment thread
glennawatson marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ protected override ApplicationInsightsFeatureUsageTrackingSession GetFeatureUsag
var telemetryConfiguration = new TelemetryConfiguration
{
DisableTelemetry = true,
ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000",
};
var telemetryClient = new TelemetryClient(telemetryConfiguration);

Expand All @@ -42,6 +43,7 @@ protected override ApplicationInsightsFeatureUsageTrackingSession GetFeatureUsag
var telemetryConfiguration = new TelemetryConfiguration
{
DisableTelemetry = true,
ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000",
};
var telemetryClient = new TelemetryClient(telemetryConfiguration);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2026 ReactiveUI. All rights reserved.
// Copyright (c) 2026 ReactiveUI. All rights reserved.
// Licensed to ReactiveUI under one or more agreements.
// ReactiveUI licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.
Expand All @@ -9,7 +9,7 @@
namespace Splat.Tests.ApplicationPerformanceMonitoring;

/// <summary>
/// Unit Tests for Application Insights Feature Usage Tracking.
/// Unit Tests for Application Insights View Tracking.
/// </summary>
public static class ApplicationInsightsViewTrackingTests
{
Expand All @@ -23,6 +23,87 @@ protected override ApplicationInsightsViewTracking GetViewTracking()
var telemetryConfiguration = new TelemetryConfiguration
{
DisableTelemetry = true,
ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000",
};
var telemetryClient = new TelemetryClient(telemetryConfiguration);

return new(telemetryClient);
}
}

/// <summary>
/// Tests for the <see cref="ApplicationInsightsViewTracking.OnViewNavigation"/> method.
/// </summary>
public sealed class OnViewNavigationMethod
{
/// <summary>
/// Verifies that tracking a page view with a valid name does not throw.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
[Test]
public async Task TracksPageView()
{
var viewTracking = CreateViewTracking();

viewTracking.OnViewNavigation("HomePage");

await Assert.That(viewTracking).IsNotNull();
}
Comment thread
glennawatson marked this conversation as resolved.

/// <summary>
/// Verifies that tracking multiple consecutive page views does not throw.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
[Test]
public async Task TracksMultiplePageViews()
{
var viewTracking = CreateViewTracking();

viewTracking.OnViewNavigation("HomePage");
viewTracking.OnViewNavigation("SettingsPage");
viewTracking.OnViewNavigation("ProfilePage");

await Assert.That(viewTracking).IsNotNull();
}

/// <summary>
/// Verifies that tracking a page view with an empty name does not throw.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
[Test]
public async Task TracksEmptyViewName()
{
var viewTracking = CreateViewTracking();

viewTracking.OnViewNavigation(string.Empty);

await Assert.That(viewTracking).IsNotNull();
}

/// <summary>
/// Verifies that tracking a page view with a name containing special characters does not throw.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
[Test]
public async Task TracksViewNameWithSpecialCharacters()
{
var viewTracking = CreateViewTracking();

viewTracking.OnViewNavigation("Views/Home Page (Main)");

await Assert.That(viewTracking).IsNotNull();
}

/// <summary>
/// Creates an <see cref="ApplicationInsightsViewTracking"/> instance configured for testing.
/// </summary>
/// <returns>A new <see cref="ApplicationInsightsViewTracking"/> instance with telemetry disabled.</returns>
private static ApplicationInsightsViewTracking CreateViewTracking()
{
var telemetryConfiguration = new TelemetryConfiguration
{
DisableTelemetry = true,
ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000",
};
var telemetryClient = new TelemetryClient(telemetryConfiguration);

Expand Down
Loading