-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Jens Henneberg <[email protected]>
- Loading branch information
1 parent
533dfa6
commit 98028e9
Showing
12 changed files
with
523 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
src/OpenFeature.Contrib.Providers.Statsig/EvaluationContextExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using OpenFeature.Model; | ||
using Statsig; | ||
|
||
namespace OpenFeature.Contrib.Providers.Statsig | ||
{ | ||
internal static class EvaluationContextExtensions | ||
{ | ||
//These keys match the keys of the statsiguser object as descibed here | ||
//https://docs.statsig.com/client/concepts/user | ||
internal const string CONTEXT_APP_VERSION = "appVersion"; | ||
internal const string CONTEXT_COUNTRY = "country"; | ||
internal const string CONTEXT_EMAIL = "email"; | ||
internal const string CONTEXT_IP = "ip"; | ||
internal const string CONTEXT_LOCALE = "locale"; | ||
internal const string CONTEXT_USER_AGENT = "userAgent"; | ||
internal const string CONTEXT_PRIVATE_ATTRIBUTES = "privateAttributes"; | ||
|
||
public static StatsigUser AsStatsigUser(this EvaluationContext evaluationContext) | ||
{ | ||
if (evaluationContext == null) | ||
return null; | ||
|
||
var user = new StatsigUser() { UserID = evaluationContext.TargetingKey }; | ||
foreach (var item in evaluationContext) | ||
{ | ||
switch (item.Key) | ||
{ | ||
case CONTEXT_APP_VERSION: | ||
user.AppVersion = item.Value.AsString; | ||
break; | ||
case CONTEXT_COUNTRY: | ||
user.Country = item.Value.AsString; | ||
break; | ||
case CONTEXT_EMAIL: | ||
user.Email = item.Value.AsString; | ||
break; | ||
case CONTEXT_IP: | ||
user.IPAddress = item.Value.AsString; | ||
break; | ||
case CONTEXT_USER_AGENT: | ||
user.UserAgent = item.Value.AsString; | ||
break; | ||
case CONTEXT_LOCALE: | ||
user.Locale = item.Value.AsString; | ||
break; | ||
case CONTEXT_PRIVATE_ATTRIBUTES: | ||
if (item.Value.IsStructure) | ||
{ | ||
var privateAttributes = item.Value.AsStructure; | ||
foreach (var items in privateAttributes) | ||
{ | ||
user.AddPrivateAttribute(items.Key, items.Value); | ||
} | ||
} | ||
break; | ||
|
||
default: | ||
user.AddCustomProperty(item.Key, item.Value.AsObject); | ||
break; | ||
} | ||
} | ||
return user; | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/OpenFeature.Contrib.Providers.Statsig/OpenFeature.Contrib.Providers.Statsig.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<PackageId>OpenFeature.Contrib.Provider.Statsig</PackageId> | ||
<VersionNumber>0.0.1</VersionNumber><!--x-release-please-version --> | ||
<VersionPrefix>$(VersionNumber)</VersionPrefix> | ||
<VersionSuffix>preview</VersionSuffix> | ||
<AssemblyVersion>$(VersionNumber)</AssemblyVersion> | ||
<FileVersion>$(VersionNumber)</FileVersion> | ||
<Description>Statsig provider for .NET</Description> | ||
<PackageReadmeFile>README.md</PackageReadmeFile> | ||
<Authors>Jens Kjær Henneberg</Authors> | ||
</PropertyGroup> | ||
<ItemGroup> | ||
<!-- make the internal methods visble to our test project --> | ||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> | ||
<_Parameter1>$(MSBuildProjectName).Test</_Parameter1> | ||
</AssemblyAttribute> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Statsig" Version="1.23.1" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<None Include="README.md" Pack="true" PackagePath="\"/> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
# Statsig Feature Flag .NET Provider | ||
|
||
The Statsig Flag provider allows you to connect to Statsig. Please note this is a minimal implementation - only `ResolveBooleanValue` is implemented. | ||
|
||
# .Net SDK usage | ||
|
||
## Install dependencies | ||
|
||
The first things we will do is install the **Open Feature SDK** and the **Statsig Feature Flag provider**. | ||
|
||
### .NET Cli | ||
```shell | ||
dotnet add package OpenFeature.Contrib.Providers.Statsig | ||
``` | ||
### Package Manager | ||
|
||
```shell | ||
NuGet\Install-Package OpenFeature.Contrib.Providers.Statsig | ||
``` | ||
### Package Reference | ||
|
||
```xml | ||
<PackageReference Include="OpenFeature.Contrib.Providers.Statsig" /> | ||
``` | ||
### Packet cli | ||
|
||
```shell | ||
paket add OpenFeature.Contrib.Providers.Statsig | ||
``` | ||
|
||
### Cake | ||
|
||
```shell | ||
// Install OpenFeature.Contrib.Providers.Statsig as a Cake Addin | ||
#addin nuget:?package=OpenFeature.Contrib.Providers.Statsig | ||
|
||
// Install OpenFeature.Contrib.Providers.Statsig as a Cake Tool | ||
#tool nuget:?package=OpenFeature.Contrib.Providers.Statsig | ||
``` | ||
|
||
## Using the Statsig Provider with the OpenFeature SDK | ||
|
||
The following example shows how to use the Statsig provider with the OpenFeature SDK. | ||
|
||
```csharp | ||
using OpenFeature; | ||
using OpenFeature.Contrib.Providers.Statsig; | ||
using System; | ||
|
||
StatsigProvider statsigProvider = new StatsigProvider("#YOUR-SDK-KEY#"); | ||
|
||
// Set the statsigProvider as the provider for the OpenFeature SDK | ||
await Api.Instance.SetProviderAsync(statsigProvider); | ||
|
||
IFeatureClient client = OpenFeature.Api.Instance.GetClient(); | ||
|
||
bool isMyAwesomeFeatureEnabled = await client.GetBooleanValue("isMyAwesomeFeatureEnabled", false); | ||
|
||
if (isMyAwesomeFeatureEnabled) | ||
{ | ||
Console.WriteLine("New Feature enabled!"); | ||
} | ||
|
||
``` | ||
|
||
### Customizing the Statsig Provider | ||
|
||
The Statsig provider can be customized by passing a `Action<StatsigServerOptions>` object to the constructor. | ||
|
||
```csharp | ||
var statsigProvider = new StatsigProvider("#YOUR-SDK-KEY#", options => options.LocalMode = true); | ||
``` | ||
|
||
For a full list of options see the [Statsig documentation](https://docs.statsig.com/server/dotnetSDK#statsig-options). | ||
|
||
## EvaluationContext and Statsig User relationship | ||
|
||
Statsig has the concept of a [StatsigUser](https://docs.statsig.com/client/concepts/user) where you can evaluate a flag based on properties. The OpenFeature SDK has the concept of an EvaluationContext which is a dictionary of string keys and values. The Statsig provider will map the EvaluationContext to a StatsigUser. | ||
|
||
The following parameters are mapped to the corresponding Statsig pre-defined parameters | ||
|
||
| EvaluationContext Key | Statsig User Parameter | | ||
|-----------------------|---------------------------| | ||
| `appVersion` | `AppVersion` | | ||
| `country` | `Country` | | ||
| `email` | `Email` | | ||
| `ip` | `Ip` | | ||
| `locale` | `Locale` | | ||
| `userAgent` | `UserAgent` | | ||
| `privateAttributes` | `PrivateAttributes` | | ||
|
||
## Known issues and limitations | ||
- Only `ResolveBooleanValue` implemented for now | ||
|
||
- Gate BooleanEvaluation with default value true cannot fallback to true. | ||
https://github.com/statsig-io/dotnet-sdk/issues/33 |
107 changes: 107 additions & 0 deletions
107
src/OpenFeature.Contrib.Providers.Statsig/StatsigProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
using OpenFeature.Constant; | ||
using OpenFeature.Error; | ||
using OpenFeature.Model; | ||
using Statsig; | ||
using Statsig.Server; | ||
using System; | ||
using System.Threading.Tasks; | ||
|
||
namespace OpenFeature.Contrib.Providers.Statsig | ||
{ | ||
/// <summary> | ||
/// An OpenFeature <see cref="FeatureProvider"/> which enables the use of the Statsig Server-Side SDK for .NET | ||
/// with OpenFeature. | ||
/// </summary> | ||
/// <example> | ||
/// var provider = new StatsigProvider("my-sdk-key"), new StatsigProviderOptions(){LocalMode = false}); | ||
/// | ||
/// OpenFeature.Api.Instance.SetProvider(provider); | ||
/// | ||
/// var client = OpenFeature.Api.Instance.GetClient(); | ||
/// </example> | ||
public sealed class StatsigProvider : FeatureProvider | ||
{ | ||
volatile bool initialized = false; | ||
private readonly Metadata _providerMetadata = new Metadata("Statsig provider"); | ||
private readonly string _sdkKey = "secret-"; //Dummy sdk key that works with local mode | ||
private readonly StatsigServerOptions _options; | ||
internal readonly ServerDriver ServerDriver; | ||
|
||
/// <summary> | ||
/// Creates new instance of <see cref="StatsigProvider"/> | ||
/// </summary> | ||
/// <param name="sdkKey">SDK Key to access Statsig.</param> | ||
/// <param name="configurationAction">The action used to configure the client.</param> | ||
public StatsigProvider(string sdkKey = null, Action<StatsigServerOptions> configurationAction = null) | ||
{ | ||
if (sdkKey != null) | ||
{ | ||
_sdkKey = sdkKey; | ||
} | ||
_options = new StatsigServerOptions(); | ||
configurationAction?.Invoke(_options); | ||
ServerDriver = new ServerDriver(_sdkKey, _options); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override Metadata GetMetadata() => _providerMetadata; | ||
|
||
/// <inheritdoc/> | ||
public override Task<ResolutionDetails<bool>> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext context = null) | ||
{ | ||
//TODO: defaultvalue = true not yet supported due to https://github.com/statsig-io/dotnet-sdk/issues/33 | ||
if (defaultValue == true) | ||
throw new FeatureProviderException(ErrorType.General, "defaultvalue = true not supported (https://github.com/statsig-io/dotnet-sdk/issues/33)"); | ||
if (GetStatus() != ProviderStatus.Ready) | ||
return Task.FromResult(new ResolutionDetails<bool>(flagKey, defaultValue, ErrorType.ProviderNotReady)); | ||
var result = ServerDriver.CheckGateSync(context.AsStatsigUser(), flagKey); | ||
return Task.FromResult(new ResolutionDetails<bool>(flagKey, result)); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override Task<ResolutionDetails<double>> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override Task<ResolutionDetails<int>> ResolveIntegerValue(string flagKey, int defaultValue, EvaluationContext context = null) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override Task<ResolutionDetails<string>> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext context = null) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override Task<ResolutionDetails<Value>> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null) | ||
{ | ||
throw new NotImplementedException(); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override ProviderStatus GetStatus() | ||
{ | ||
return initialized ? ProviderStatus.Ready : ProviderStatus.NotReady; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override async Task Initialize(EvaluationContext context) | ||
{ | ||
var initResult = await ServerDriver.Initialize(); | ||
if (initResult == InitializeResult.Success || initResult == InitializeResult.LocalMode || initResult == InitializeResult.AlreadyInitialized) | ||
{ | ||
initialized = true; | ||
} | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override Task Shutdown() | ||
{ | ||
return ServerDriver.Shutdown(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
0.0.1 |
Oops, something went wrong.