Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/dry-owls-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"PostHog": patch
---

Refactor duplicate internal SDK code paths without changing public API behavior.
24 changes: 10 additions & 14 deletions samples/PostHog.Example.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,20 @@ static string Prompt(string message)
return Console.ReadLine()?.Trim() ?? string.Empty;
}

static async Task RunCaptureExamples(PostHogClient posthog)
static string StartExampleSection(string title)
{
Console.WriteLine("\n" + new string('=', 60));
Console.WriteLine("CAPTURE EVENTS");
Console.WriteLine(title);
Console.WriteLine(new string('=', 60));

var distinctId = $"user_{Guid.NewGuid():N}";
Console.WriteLine($"\nUsing distinct ID: {distinctId}");
return distinctId;
}

static async Task RunCaptureExamples(PostHogClient posthog)
{
var distinctId = StartExampleSection("CAPTURE EVENTS");

// Simple capture
Console.WriteLine("\n📤 Capturing 'page_view' event…");
Expand Down Expand Up @@ -191,12 +197,7 @@ static async Task RunCaptureExamples(PostHogClient posthog)

static async Task RunIdentifyExamples(PostHogClient posthog)
{
Console.WriteLine("\n" + new string('=', 60));
Console.WriteLine("IDENTIFY USERS");
Console.WriteLine(new string('=', 60));

var distinctId = $"user_{Guid.NewGuid():N}";
Console.WriteLine($"\nUsing distinct ID: {distinctId}");
var distinctId = StartExampleSection("IDENTIFY USERS");

// Identify with properties
Console.WriteLine("\n👤 Identifying user with properties…");
Expand Down Expand Up @@ -232,12 +233,7 @@ await posthog.IdentifyAsync(

static async Task RunFeatureFlagExamples(PostHogClient posthog)
{
Console.WriteLine("\n" + new string('=', 60));
Console.WriteLine("FEATURE FLAGS");
Console.WriteLine(new string('=', 60));

var distinctId = $"user_{Guid.NewGuid():N}";
Console.WriteLine($"\nUsing distinct ID: {distinctId}");
var distinctId = StartExampleSection("FEATURE FLAGS");

// Check a simple boolean flag
Console.WriteLine("\n🚩 Checking feature flag 'new-dashboard'…");
Expand Down
18 changes: 14 additions & 4 deletions src/PostHog/Capture/CaptureExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,15 +331,22 @@ public static bool CapturePageView(
string pagePath,
Dictionary<string, object>? properties,
bool sendFeatureFlags)
{
if (!sendFeatureFlags)
{
return client.CapturePageView(distinctId, pagePath, properties);
}

#pragma warning disable CS0618
=> NotNull(client).CaptureSpecialEvent(
return NotNull(client).CaptureSpecialEvent(
distinctId,
eventName: "$pageview",
eventPropertyName: "$current_url",
eventPropertyValue: pagePath,
properties,
sendFeatureFlags);
sendFeatureFlags: true);
#pragma warning restore CS0618
}

/// <summary>
/// Captures a Page View ($pageview) event.
Expand Down Expand Up @@ -516,12 +523,15 @@ public static bool CaptureSurveyDismissed(
string distinctId,
string surveyId,
Dictionary<string, object>? properties)
=> NotNull(client).CaptureSpecialEvent(
{
const string eventName = "survey dismissed";
return NotNull(client).CaptureSpecialEvent(
distinctId,
eventName: "survey dismissed",
eventName,
eventPropertyName: "$survey_id",
eventPropertyValue: surveyId,
properties);
}

static bool CaptureSpecialEvent(
this IPostHogClient client,
Expand Down
43 changes: 22 additions & 21 deletions src/PostHog/Extensions/GroupIdentifyAsyncExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@ public static async Task<ApiResult> GroupIdentifyAsync(
StringOrValue<int> key,
string name,
Dictionary<string, object>? properties)
{
properties ??= new Dictionary<string, object>();
properties["name"] = name;
return await NotNull(client).GroupIdentifyAsync(type, key, properties, CancellationToken.None);
}
=> await client.GroupIdentifyAsync(type, key, name, properties, CancellationToken.None);

/// <summary>
/// Sets a groups properties, which allows asking questions like "Who are the most active companies"
Expand All @@ -49,11 +45,7 @@ public static async Task<ApiResult> GroupIdentifyAsync(
StringOrValue<int> key,
string name,
Dictionary<string, object>? properties)
{
properties ??= new Dictionary<string, object>();
properties["name"] = name;
return await NotNull(client).GroupIdentifyAsync(distinctId, type, key, properties, CancellationToken.None);
}
=> await client.GroupIdentifyAsync(distinctId, type, key, name, properties, CancellationToken.None);

/// <summary>
/// Sets a groups properties, which allows asking questions like "Who are the most active companies"
Expand All @@ -73,11 +65,7 @@ public static async Task<ApiResult> GroupIdentifyAsync(
string name,
Dictionary<string, object>? properties,
CancellationToken cancellationToken)
{
properties ??= new Dictionary<string, object>();
properties["name"] = name;
return await NotNull(client).GroupIdentifyAsync(type, key, properties, cancellationToken);
}
=> await GroupIdentifyWithNameAsync(NotNull(client), distinctId: null, type, key, name, properties, cancellationToken);

/// <summary>
/// Sets a groups properties, which allows asking questions like "Who are the most active companies"
Expand All @@ -99,11 +87,7 @@ public static async Task<ApiResult> GroupIdentifyAsync(
string name,
Dictionary<string, object>? properties,
CancellationToken cancellationToken)
{
properties ??= new Dictionary<string, object>();
properties["name"] = name;
return await NotNull(client).GroupIdentifyAsync(distinctId, type, key, properties, cancellationToken);
}
=> await GroupIdentifyWithNameAsync(NotNull(client), distinctId, type, key, name, properties, cancellationToken);

/// <summary>
/// Sets a groups properties, which allows asking questions like "Who are the most active companies"
Expand Down Expand Up @@ -147,4 +131,21 @@ public static async Task<ApiResult> GroupIdentifyAsync(
key,
name,
properties: new Dictionary<string, object>());
}

static async Task<ApiResult> GroupIdentifyWithNameAsync(
IPostHogClient client,
string? distinctId,
string type,
StringOrValue<int> key,
string name,
Dictionary<string, object>? properties,
CancellationToken cancellationToken)
{
properties ??= new Dictionary<string, object>();
properties["name"] = name;

return distinctId is null
? await client.GroupIdentifyAsync(type, key, properties, cancellationToken)
: await client.GroupIdentifyAsync(distinctId, type, key, properties, cancellationToken);
}
}
13 changes: 7 additions & 6 deletions src/PostHog/NoOpPostHogClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace PostHog.Sdk;
internal sealed class NoOpPostHogClient : IPostHogClient, IFeatureFlagEvaluationsHost
{
static readonly IReadOnlyDictionary<string, FeatureFlag> EmptyFeatureFlags = new Dictionary<string, FeatureFlag>();
static readonly Task<ApiResult> NoOpApiResultTask = Task.FromResult(new ApiResult(0));

NoOpPostHogClient()
{
Expand All @@ -17,29 +18,29 @@ internal sealed class NoOpPostHogClient : IPostHogClient, IFeatureFlagEvaluation
internal static NoOpPostHogClient Instance { get; } = new();

public Task<ApiResult> AliasAsync(string previousId, string newId, CancellationToken cancellationToken)
=> Task.FromResult(new ApiResult(0));
=> NoOpApiResultTask;

public Task<ApiResult> IdentifyAsync(
string distinctId,
Dictionary<string, object>? personPropertiesToSet,
Dictionary<string, object>? personPropertiesToSetOnce,
CancellationToken cancellationToken)
=> Task.FromResult(new ApiResult(0));
=> NoOpApiResultTask;

public Task<ApiResult> GroupIdentifyAsync(
string type,
StringOrValue<int> key,
Dictionary<string, object>? properties,
CancellationToken cancellationToken)
=> Task.FromResult(new ApiResult(0));
=> NoOpApiResultTask;

public Task<ApiResult> GroupIdentifyAsync(
string distinctId,
string type,
StringOrValue<int> key,
Dictionary<string, object>? properties,
CancellationToken cancellationToken)
=> Task.FromResult(new ApiResult(0));
=> NoOpApiResultTask;

public bool Capture(
string distinctId,
Expand All @@ -48,7 +49,7 @@ public bool Capture(
GroupCollection? groups,
bool sendFeatureFlags,
DateTimeOffset? timestamp = null)
=> false;
=> Capture(distinctId, eventName, properties, groups, flags: null, timestamp);

public bool Capture(
string distinctId,
Expand All @@ -66,7 +67,7 @@ public bool CaptureException(
GroupCollection? groups,
bool sendFeatureFlags,
DateTimeOffset? timestamp = null)
=> false;
=> CaptureException(exception, distinctId, properties, groups, flags: null, timestamp);

public bool CaptureException(
Exception exception,
Expand Down
25 changes: 9 additions & 16 deletions src/PostHog/PostHogClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,22 +216,7 @@ public async Task<ApiResult> GroupIdentifyAsync(
StringOrValue<int> key,
Dictionary<string, object>? properties,
CancellationToken cancellationToken)
{
if (CheckDisabledAndLog(nameof(GroupIdentifyAsync)))
{
return NoOpApiResult;
}

try
{
return await _apiClient.GroupIdentifyAsync(type, key, properties, cancellationToken);
}
catch (Exception e) when (e is not ArgumentException and not NullReferenceException and not OperationCanceledException)
{
_logger.LogErrorApiCallFailed(e, nameof(GroupIdentifyAsync));
return NoOpApiResult;
}
}
=> await GroupIdentifyCoreAsync(type, key, properties, distinctId: null, cancellationToken);

/// <inheritdoc/>
public async Task<ApiResult> GroupIdentifyAsync(
Expand All @@ -240,6 +225,14 @@ public async Task<ApiResult> GroupIdentifyAsync(
StringOrValue<int> key,
Dictionary<string, object>? properties,
CancellationToken cancellationToken)
=> await GroupIdentifyCoreAsync(type, key, properties, distinctId, cancellationToken);

async Task<ApiResult> GroupIdentifyCoreAsync(
string type,
StringOrValue<int> key,
Dictionary<string, object>? properties,
string? distinctId,
CancellationToken cancellationToken)
{
if (CheckDisabledAndLog(nameof(GroupIdentifyAsync)))
{
Expand Down
17 changes: 8 additions & 9 deletions tests/TestLibrary/Fakes/FakeHttpMessageHandlerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ internal static class FakeHttpMessageHandlerExtensions
static readonly Uri FlagsUrl = new("https://us.i.posthog.com/flags/?v=2");

public static FakeHttpMessageHandler.RequestHandler AddCaptureResponse(this FakeHttpMessageHandler handler) =>
handler.AddResponse(
new Uri("https://us.i.posthog.com/capture"),
HttpMethod.Post,
responseBody: new { status = 1 });
handler.AddIngestionResponse("capture");

public static FakeHttpMessageHandler.RequestHandler AddBatchResponse(this FakeHttpMessageHandler handler) =>
handler.AddIngestionResponse("batch");

static FakeHttpMessageHandler.RequestHandler AddIngestionResponse(
this FakeHttpMessageHandler handler,
string endpoint) =>
handler.AddResponse(
new Uri("https://us.i.posthog.com/batch"),
new Uri($"https://us.i.posthog.com/{endpoint}"),
HttpMethod.Post,
responseBody: new { status = 1 });

Expand Down Expand Up @@ -156,10 +158,7 @@ public static FakeHttpMessageHandler.RequestHandler AddDecryptedPayloadResponse(
this FakeHttpMessageHandler handler,
string key,
string responseBody) =>
handler.AddResponse(
new Uri($"https://us.i.posthog.com/api/projects/@current/feature_flags/{key}/remote_config?token=fake-project-token"),
HttpMethod.Get,
responseBody: responseBody);
handler.AddRemoteConfigResponse(key, responseBody);

static T Deserialize<T>(string json) => JsonSerializer.Deserialize<T>(json, JsonSerializerHelper.Options)
?? throw new ArgumentException("Json is invalid and deserializes to null", nameof(json));
Expand Down
Loading
Loading