diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/LaunchDarkly.OpenFeature.ServerProvider.csproj b/src/LaunchDarkly.OpenFeature.ServerProvider/LaunchDarkly.OpenFeature.ServerProvider.csproj index dacef0e..254e5e2 100644 --- a/src/LaunchDarkly.OpenFeature.ServerProvider/LaunchDarkly.OpenFeature.ServerProvider.csproj +++ b/src/LaunchDarkly.OpenFeature.ServerProvider/LaunchDarkly.OpenFeature.ServerProvider.csproj @@ -41,7 +41,7 @@ - + diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/Provider.cs b/src/LaunchDarkly.OpenFeature.ServerProvider/Provider.cs index 1435ee4..c04b0c2 100644 --- a/src/LaunchDarkly.OpenFeature.ServerProvider/Provider.cs +++ b/src/LaunchDarkly.OpenFeature.ServerProvider/Provider.cs @@ -183,7 +183,7 @@ await EventChannel.Writer.WriteAsync(new ProviderEventPayload { ProviderName = _metadata.Name, Type = ProviderEventTypes.ProviderConfigurationChanged, - FlagsChanged = new List {changeEvent.Key}, + FlagsChanged = new List { changeEvent.Key }, }).ConfigureAwait(false); } catch (Exception e) @@ -217,5 +217,24 @@ private void StatusChangeHandler(object sender, DataSourceStatus status) break; } } + + /// + public override void Track(string trackingEventName, EvaluationContext evaluationContext = null, TrackingEventDetails trackingEventDetails = default) + { + var (value, details) = trackingEventDetails.ToLdValue(); + + if (value.HasValue) + { + _client.Track(trackingEventName, _contextConverter.ToLdContext(evaluationContext), details, value.Value); + } + else if (details.Type != LdValueType.Null) + { + _client.Track(trackingEventName, _contextConverter.ToLdContext(evaluationContext), details); + } + else + { + _client.Track(trackingEventName, _contextConverter.ToLdContext(evaluationContext)); + } + } } } diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/TrackingEventDetailsExtensions.cs b/src/LaunchDarkly.OpenFeature.ServerProvider/TrackingEventDetailsExtensions.cs new file mode 100644 index 0000000..e5bc065 --- /dev/null +++ b/src/LaunchDarkly.OpenFeature.ServerProvider/TrackingEventDetailsExtensions.cs @@ -0,0 +1,39 @@ +using LaunchDarkly.Sdk; +using OpenFeature.Model; + +namespace LaunchDarkly.OpenFeature.ServerProvider +{ + internal static class TrackingEventDetailsExtensions + { + /// + /// Extract an OpenFeature into an . + /// + /// The value to extract + public static (double?, LdValue) ToLdValue(this TrackingEventDetails trackingEventDetails) + { + if (trackingEventDetails == null) + { + return (null, LdValue.Null); + } + + var value = trackingEventDetails.Value; + + LdValue details; + if (trackingEventDetails.Count == 0) + { + details = LdValue.Null; + } + else + { + var builder = LdValue.BuildObject(); + foreach (var keyvalue in trackingEventDetails) + { + builder.Add(keyvalue.Key, keyvalue.Value.ToLdValue()); + } + details = builder.Build(); + } + + return (value, details); + } + } +} diff --git a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/LaunchDarkly.OpenFeature.ServerProvider.Tests.csproj b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/LaunchDarkly.OpenFeature.ServerProvider.Tests.csproj index 3209d74..e364126 100644 --- a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/LaunchDarkly.OpenFeature.ServerProvider.Tests.csproj +++ b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/LaunchDarkly.OpenFeature.ServerProvider.Tests.csproj @@ -32,7 +32,7 @@ - + diff --git a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderTests.cs b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderTests.cs index f2a239c..ec642dd 100644 --- a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderTests.cs +++ b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderTests.cs @@ -272,5 +272,73 @@ public async Task ItEmitsConfigurationChangedEvents() Assert.Single(eventPayloadB?.FlagsChanged ?? new List()); Assert.NotEqual(eventPayloadA?.FlagsChanged[0], eventPayloadB?.FlagsChanged[0]); } + + [Fact(Timeout = 5000)] + public void ItTracksCustomEvents() + { + var evaluationContext = EvaluationContext.Builder() + .Set("targetingKey", "the-key") + .Build(); + var mock = new Mock(); + mock.Setup(l => l.GetLogger()) + .Returns(Components.NoLogging.Build(null).LogAdapter.Logger(null)); + mock.Setup(l => l.Track("event-key-123abc", _converter.ToLdContext(evaluationContext))).Verifiable(); + var provider = new Provider(mock.Object); + + provider.Track("event-key-123abc", evaluationContext); + + mock.Verify(); + } + + [Fact(Timeout = 5000)] + public void ItTracksCustomEventsWithValue() + { + var evaluationContext = EvaluationContext.Builder() + .Set("targetingKey", "the-key") + .Build(); + var mock = new Mock(); + mock.Setup(l => l.GetLogger()) + .Returns(Components.NoLogging.Build(null).LogAdapter.Logger(null)); + mock.Setup(l => l.Track("event-key-123abc", _converter.ToLdContext(evaluationContext), LdValue.Null, 99.77)).Verifiable(); + var provider = new Provider(mock.Object); + + provider.Track("event-key-123abc", evaluationContext, TrackingEventDetails.Builder().SetValue(99.77).Build()); + + mock.Verify(); + } + + [Fact(Timeout = 5000)] + public void ItTracksCustomEventsWithDetails() + { + var evaluationContext = EvaluationContext.Builder() + .Set("targetingKey", "the-key") + .Build(); + var mock = new Mock(); + mock.Setup(l => l.GetLogger()) + .Returns(Components.NoLogging.Build(null).LogAdapter.Logger(null)); + mock.Setup(l => l.Track("event-key-123abc", _converter.ToLdContext(evaluationContext), LdValue.BuildObject().Set("color", "red").Build())).Verifiable(); + var provider = new Provider(mock.Object); + + provider.Track("event-key-123abc", evaluationContext, TrackingEventDetails.Builder().Set("color", "red").Build()); + + mock.Verify(); + } + + [Fact(Timeout = 5000)] + public void ItTracksCustomEventsWithDetailsAndValue() + { + var evaluationContext = EvaluationContext.Builder() + .Set("targetingKey", "the-key") + .Build(); + var mock = new Mock(); + mock.Setup(l => l.GetLogger()) + .Returns(Components.NoLogging.Build(null).LogAdapter.Logger(null)); + mock.Setup(l => l.Track("event-key-123abc", _converter.ToLdContext(evaluationContext), LdValue.BuildObject().Set("currency", "USD").Build(), 99.77)).Verifiable(); + var provider = new Provider(mock.Object); + + provider.Track("event-key-123abc", evaluationContext, TrackingEventDetails.Builder().SetValue(99.77).Set("currency", "USD").Build()); + + mock.Verify(); + } } } diff --git a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/TrackingEventDetailsExtensionsTest.cs b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/TrackingEventDetailsExtensionsTest.cs new file mode 100644 index 0000000..748e774 --- /dev/null +++ b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/TrackingEventDetailsExtensionsTest.cs @@ -0,0 +1,56 @@ +using LaunchDarkly.Sdk; +using OpenFeature.Model; +using Xunit; + +namespace LaunchDarkly.OpenFeature.ServerProvider.Tests +{ + public class TrackingEventDetailsExtensionsTest + { + [Fact] + public void ItCanHandleValuesAndDetails() + { + var trackingEventDetails = TrackingEventDetails.Builder().SetValue(99.77).Set("currency", "USD").Build(); + var (value, details) = trackingEventDetails.ToLdValue(); + Assert.Equal(LdValueType.Object, details.Type); + Assert.Equal(LdValue.Of("USD"), details.Get("currency")); + Assert.Equal(99.77, value); + } + + [Fact] + public void ItCanHandleDetailsOnly() + { + var trackingEventDetails = TrackingEventDetails.Builder().Set("color", "red").Build(); + var (value, details) = trackingEventDetails.ToLdValue(); + Assert.Equal(LdValueType.Object, details.Type); + Assert.Equal(LdValue.Of("red"), details.Get("color")); + Assert.Null(value); + } + + [Fact] + public void ItCanHandleValuesOnly() + { + var trackingEventDetails = TrackingEventDetails.Builder().SetValue(99.77).Build(); + var (value, details) = trackingEventDetails.ToLdValue(); + Assert.Equal(LdValueType.Null, details.Type); + Assert.Equal(99.77, value); + } + + [Fact] + public void ItCanHandleEmptyStructures() + { + var trackingEventDetails = TrackingEventDetails.Empty; + var (value, details) = trackingEventDetails.ToLdValue(); + Assert.Equal(LdValueType.Null, details.Type); + Assert.Null(value); + } + + [Fact] + public void ItCanHandleNull() + { + TrackingEventDetails trackingEventDetails = null; + var (value, details) = trackingEventDetails.ToLdValue(); + Assert.Equal(LdValueType.Null, details.Type); + Assert.Null(value); + } + } +}