Skip to content

Commit

Permalink
Feature: event system and impression data events (#113)
Browse files Browse the repository at this point in the history
* event system callback configuration on unleash-instance

* improve eventconfig callback

* add impressionData field to featuretoggle

* add impressionevent logic and tests

* improvement: document new events feature

* add more tests

* add some serialization testing for impressiondata
  • Loading branch information
daveleek committed Nov 30, 2022
1 parent 0943a2d commit f74af38
Show file tree
Hide file tree
Showing 16 changed files with 448 additions and 14 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,27 @@ When your application shuts down, remember to dispose the unleash instance.
unleash?.Dispose()
```

### Handling events
Currently supported events:
- [Impression data events](https://docs.getunleash.io/advanced/impression-data#impression-event-data)

```csharp

var settings = new UnleashSettings()
{
// ...
};

var unleash = new DefaultUnleash(settings);

// Set up handling of impression events
unleash.ConfigureEvents(cfg =>
{
cfg.ImpressionEvent = evt => { Console.WriteLine($"{evt.FeatureName}: {evt.Enabled}"); };
});

```

### Configuring projects in unleash client

If you're organizing your feature toggles in `Projects` in Unleash Enterprise, you can specify the `ProjectId` on the `UnleashSettings` to select which project to fetch feature toggles for.
Expand Down
54 changes: 54 additions & 0 deletions src/Unleash/DefaultUnleash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public DefaultUnleash(UnleashSettings settings, bool overrideDefaultStrategies,
/// <inheritdoc />
public ICollection<FeatureToggle> FeatureToggles => services.ToggleCollection.Instance.Features;

private EventCallbackConfig EventConfig { get; set; }

/// <inheritdoc />
public bool IsEnabled(string toggleName)
{
Expand Down Expand Up @@ -125,6 +127,9 @@ private bool CheckIsEnabled(string toggleName, UnleashContext context, bool defa
}

RegisterCount(toggleName, enabled);

if (featureToggle?.ImpressionData ?? false) EmitImpressionEvent("isEnabled", context, enabled, featureToggle.Name);

return enabled;
}

Expand All @@ -146,6 +151,9 @@ public Variant GetVariant(string toggleName, UnleashContext context, Variant def
var variant = enabled ? VariantUtils.SelectVariant(toggle, context, defaultValue) : defaultValue;

RegisterVariant(toggleName, variant);

if (toggle?.ImpressionData ?? false) EmitImpressionEvent("getVariant", context, enabled, toggle.Name, variant.Name);

return variant;
}

Expand Down Expand Up @@ -235,6 +243,52 @@ private IEnumerable<Constraint> ResolveConstraints(ActivationStrategy activation
}
}

public void ConfigureEvents(Action<EventCallbackConfig> callback)
{
if (callback == null)
{
Logger.Error($"UNLEASH: Unleash->ConfigureEvents parameter callback is null");
return;
}

try
{
var evtConfig = new EventCallbackConfig();
callback(evtConfig);
EventConfig = evtConfig;
}
catch (Exception ex)
{
Logger.Error($"UNLEASH: Unleash->ConfigureEvents executing callback threw exception: {ex.Message}");
}
}

private void EmitImpressionEvent(string type, UnleashContext context, bool enabled, string name, string variant = null)
{
if (EventConfig.ImpressionEvent == null)
{
Logger.Error($"UNLEASH: Unleash->ImpressionData callback is null, unable to emit event");
return;
}

try
{
EventConfig.ImpressionEvent(new ImpressionEvent
{
Type = type,
Context = context,
EventId = Guid.NewGuid().ToString(),
Enabled = enabled,
FeatureName = name,
Variant = variant
});
}
catch (Exception ex)
{
Logger.Error($"UNLEASH: Emitting impression event callback threw exception: {ex.Message}");
}
}

public void Dispose()
{
services?.Dispose();
Expand Down
2 changes: 2 additions & 0 deletions src/Unleash/IUnleash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,7 @@ public interface IUnleash : IDisposable
/// /// <param name="context">The Unleash context to evaluate the toggle state against.</param>
/// <returns>A list of available variants.</returns>
IEnumerable<VariantDefinition> GetVariants(string toggleName, UnleashContext context);

void ConfigureEvents(Action<EventCallbackConfig> config);
}
}
11 changes: 11 additions & 0 deletions src/Unleash/Internal/EventCallbackConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Unleash.Internal
{
public class EventCallbackConfig
{
public Action<ImpressionEvent> ImpressionEvent { get; set; }
}
}
7 changes: 5 additions & 2 deletions src/Unleash/Internal/FeatureToggle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,28 @@ namespace Unleash.Internal
{
public class FeatureToggle
{
public FeatureToggle(string name, string type, bool enabled, List<ActivationStrategy> strategies, List<VariantDefinition> variants = null)
public FeatureToggle(string name, string type, bool enabled, bool impressionData, List<ActivationStrategy> strategies, List<VariantDefinition> variants = null)
{
Name = name;
Type = type;
Enabled = enabled;
ImpressionData = impressionData;
Strategies = strategies;
Variants = variants ?? new List<VariantDefinition>();
}

public string Name { get; }
public string Type { get; }
public bool Enabled { get; }
public bool ImpressionData { get; }

public List<ActivationStrategy> Strategies { get; }

public List<VariantDefinition> Variants { get; }

public override string ToString()
{
return $"FeatureToggle{{name=\'{Name}{'\''}, enabled={Enabled}, strategies=\'{Strategies}{'\''}{'}'}";
return $"FeatureToggle{{name=\'{Name}{'\''}, enabled={Enabled}, impressionData={ImpressionData}, strategies=\'{Strategies}{'\''}{'}'}";
}
}
}
16 changes: 16 additions & 0 deletions src/Unleash/Internal/ImpressionEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Unleash.Internal
{
public class ImpressionEvent
{
public string Type { get; set; }
public string EventId { get; set; }
public UnleashContext Context { get; set; }
public bool Enabled { get; set; }
public string FeatureName { get; set; }
public string Variant { get; set; }
}
}
4 changes: 2 additions & 2 deletions src/Unleash/Serialization/JsonSerializerTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ public static class JsonSerializerTester
{
private static readonly ToggleCollection Toggles = new ToggleCollection(new List<FeatureToggle>
{
new FeatureToggle("Feature1", "release", true, new List<ActivationStrategy>()
new FeatureToggle("Feature1", "release", true, false, new List<ActivationStrategy>()
{
new ActivationStrategy("remoteAddress", new Dictionary<string, string>()
{
{"IPs", "127.0.0.1"}
})
}),
new FeatureToggle("feature2", "release", false, new List<ActivationStrategy>()
new FeatureToggle("feature2", "release", false, false, new List<ActivationStrategy>()
{
new ActivationStrategy("userWithId", new Dictionary<string, string>()
{
Expand Down
30 changes: 30 additions & 0 deletions tests/Unleash.Tests/App_Data/impressiondata-v2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"version": 2,
"features": [
{
"name": "Tests only",
"description": "Where name has test in it",
"enabled": true,
"impressionData": true,
"strategies": [
{
"name": "default",
"parameters": {},
"segments": [ 1 ]
}
]
}
],
"segments": [
{
"id": 1,
"constraints": [
{
"contextName": "name",
"operator": "STR_CONTAINS",
"value": "test"
}
]
}
]
}
31 changes: 31 additions & 0 deletions tests/Unleash.Tests/DefaultUnleashTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using FluentAssertions;
using NUnit.Framework;
using System;

namespace Unleash.Tests
{
public class DefaultUnleashTests
{
[Test]
public void ConfigureEvents_should_invoke_callback()
{
// Arrange
var settings = new UnleashSettings
{
AppName = "testapp",
};

var unleash = new DefaultUnleash(settings);
var callbackCalled = false;

// Act
unleash.ConfigureEvents(cfg =>
{
callbackCalled = true;
});

// Assert
callbackCalled.Should().BeTrue();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ private static ToggleCollection GetTestToggles()
{
return new ToggleCollection(new List<FeatureToggle>
{
new FeatureToggle("one-enabled", "release", true, new List<ActivationStrategy>()
new FeatureToggle("one-enabled", "release", true, false, new List<ActivationStrategy>()
{
new ActivationStrategy("userWithId", new Dictionary<string, string>(){
{"userIds", "userA" }
Expand All @@ -29,7 +29,7 @@ private static ToggleCollection GetTestToggles()
new VariantDefinition("Ab", 34, null, new List<VariantOverride>{ new VariantOverride("context", new[] { "a", "b"}) }),
}
),
new FeatureToggle("one-disabled", "release", false, new List<ActivationStrategy>()
new FeatureToggle("one-disabled", "release", false, false, new List<ActivationStrategy>()
{
new ActivationStrategy("userWithId", new Dictionary<string, string>()
{
Expand Down

0 comments on commit f74af38

Please sign in to comment.