Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
34 changes: 23 additions & 11 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,38 @@
/// </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);
public void OnViewNavigation(string name) => OnViewNavigation(
name,
new Dictionary<string, string>());

/// <summary>
/// Track a View Navigation with Extended Data.
/// Track a view navigation using name and extended properties.
/// </summary>
/// <param name="telemetry">Telemetry data.</param>
public void OnViewNavigation(PageViewTelemetry telemetry)
/// <remarks>
/// See https://github.com/microsoft/ApplicationInsights-dotnet/tree/main/BASE#tracking-page-views for details on underlying usage.
/// </remarks>
/// <param name="name">Name of the view.</param>
/// <param name="extendedProperties">Set of extended properties to send with the event. NOTE: if you set PageName in the collection, it will be overridden using <see cref="name"/>.</param>

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (macos-latest)

XML comment has cref attribute 'name' that could not be resolved

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (macos-latest)

XML comment has cref attribute 'name' that could not be resolved

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (macos-latest)

XML comment has cref attribute 'name' that could not be resolved

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-windows

XML comment has cref attribute 'name' that could not be resolved

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-windows

XML comment has cref attribute 'name' that could not be resolved

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-windows

XML comment has cref attribute 'name' that could not be resolved

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-windows

XML comment has cref attribute 'name' that could not be resolved

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (ubuntu-latest)

XML comment has cref attribute 'name' that could not be resolved

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (ubuntu-latest)

XML comment has cref attribute 'name' that could not be resolved

Check warning on line 37 in src/Splat.ApplicationInsights/ApplicationInsightsViewTracking.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (ubuntu-latest)

XML comment has cref attribute 'name' that could not be resolved
public void OnViewNavigation(
string name,
IDictionary<string, string> extendedProperties)
{
_ = GetPageViewTelemetry();
telemetryClient.TrackPageView(telemetry);
}
// need to look at whether the standard properties of the Javascript SDK are supported in the .NET SDK (or rather the Azure Monitor when it rewrites the payload), but for now we'll just leave as minimal and allow injection by caller.
// reference: https://github.com/microsoft/ApplicationInsights-JS/blob/b6de144e27629b2d50e05ceb3885ee51b4fa0e2b/API-reference.md
extendedProperties ??= new Dictionary<string, string>();
extendedProperties["PageName"] = name;

internal static PageViewTelemetry GetPageViewTelemetry() => new();
telemetryClient.TrackEvent(
"PageView",
extendedProperties);
}
}
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 @@
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.

Check warning on line 35 in src/tests/Splat.ApplicationInsights.Tests/ApplicationInsightsViewTrackingTests.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (macos-latest)

Ambiguous reference in cref attribute: 'ApplicationInsightsViewTracking.OnViewNavigation'. Assuming 'ApplicationInsightsViewTracking.OnViewNavigation(string)', but could have also matched other overloads including 'ApplicationInsightsViewTracking.OnViewNavigation(string, IDictionary<string, string>)'.

Check warning on line 35 in src/tests/Splat.ApplicationInsights.Tests/ApplicationInsightsViewTrackingTests.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (macos-latest)

Ambiguous reference in cref attribute: 'ApplicationInsightsViewTracking.OnViewNavigation'. Assuming 'ApplicationInsightsViewTracking.OnViewNavigation(string)', but could have also matched other overloads including 'ApplicationInsightsViewTracking.OnViewNavigation(string, IDictionary<string, string>)'.

Check warning on line 35 in src/tests/Splat.ApplicationInsights.Tests/ApplicationInsightsViewTrackingTests.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (ubuntu-latest)

Ambiguous reference in cref attribute: 'ApplicationInsightsViewTracking.OnViewNavigation'. Assuming 'ApplicationInsightsViewTracking.OnViewNavigation(string)', but could have also matched other overloads including 'ApplicationInsightsViewTracking.OnViewNavigation(string, IDictionary<string, string>)'.

Check warning on line 35 in src/tests/Splat.ApplicationInsights.Tests/ApplicationInsightsViewTrackingTests.cs

View workflow job for this annotation

GitHub Actions / build / build-unix (ubuntu-latest)

Ambiguous reference in cref attribute: 'ApplicationInsightsViewTracking.OnViewNavigation'. Assuming 'ApplicationInsightsViewTracking.OnViewNavigation(string)', but could have also matched other overloads including 'ApplicationInsightsViewTracking.OnViewNavigation(string, IDictionary<string, string>)'.
/// </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();
}

/// <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