Skip to content

Commit

Permalink
Adding Custom Field Support
Browse files Browse the repository at this point in the history
  • Loading branch information
hermeswaldemarin committed Nov 30, 2023
1 parent dc12a9d commit 51a219e
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 5 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/main.yml
@@ -0,0 +1,20 @@
name: CI
on: [push]
jobs:
build:
runs-on: windows-2019
steps:
- uses: actions/checkout@v3

- uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.0.x'
dotnet-quality: 'preview'
env:
NUGET_AUTH_TOKEN: ${{secrets.NUGET_AUTH_TOKEN}}

- name: Run Build
run: dotnet build src/ABSmartly.Sdk

- name: Run Tests
run: dotnet test
30 changes: 30 additions & 0 deletions .github/workflows/publish.yml
@@ -0,0 +1,30 @@
name: Publish Nuget
on:
push:
branches:
- "main"
tags:
- v*
jobs:
build:
runs-on: windows-2019

steps:
- uses: actions/checkout@v3

- uses: actions/setup-dotnet@v3
with:
dotnet-version: '5.0.x'
dotnet-quality: 'preview'
env:
NUGET_AUTH_TOKEN: ${{secrets.NUGET_AUTH_TOKEN}}

- name: Run Build
run: dotnet build
env:
NUGET_AUTH_TOKEN: ${{secrets.NUGET_AUTH_TOKEN}}

- name: Run Release
run: dotnet nuget push bin/Debug/ --api-key $NUGET_AUTH_TOKEN --source https://api.nuget.org/v3/index.json
env:
NUGET_AUTH_TOKEN: ${{secrets.NUGET_AUTH_TOKEN}}
4 changes: 2 additions & 2 deletions src/ABSmartly.Sdk/ABSmartly.Sdk.csproj
Expand Up @@ -6,7 +6,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>ABSmartly.Sdk</AssemblyName>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageVersion>1.1.1</PackageVersion>
<PackageVersion>1.2.1</PackageVersion>
<Title>A/B Smartly DotNet SDK</Title>
<Authors>A/B Smartly</Authors>
<Description>The A/B Smartly DotNet SDK is a client SDK for A/B Smartly service</Description>
Expand All @@ -19,11 +19,11 @@
<TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.1.1.77</FileVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
</PropertyGroup>

<ItemGroup>
<Folder Include="JsonExpressions\Operators\" />
<Folder Include="Models\" />
</ItemGroup>

<ItemGroup>
Expand Down
126 changes: 126 additions & 0 deletions src/ABSmartly.Sdk/Context.cs
Expand Up @@ -55,6 +55,8 @@ public class Context : IContext, IDisposable, IAsyncDisposable

private bool _failed;
private Dictionary<string, ExperimentVariables> _index;
private Dictionary<string, Dictionary<string, ContextCustomFieldValue>> _contextCustomFields;

private DictionaryLockableAdapter<string, ExperimentVariables> _indexVariables;

private volatile int _pendingCount;
Expand Down Expand Up @@ -460,6 +462,84 @@ private ExperimentVariables GetExperiment(string experimentName)
_dataLock.ExitReadLock();
}
}

public List<String> GetCustomFieldKeys()
{
try
{
_dataLock.EnterReadLock();

var keys = new List<String>();

foreach (var experiment in _data.Experiments)
{
var customFieldValues = experiment.CustomFieldValues;
if (customFieldValues != null)
{
foreach (var customFieldValue in customFieldValues)
{
keys.Add(customFieldValue.Name);
}
}
}

return keys.OrderBy(q => q).Distinct().ToList();
}
finally
{
_dataLock.ExitReadLock();
}
}

public Object GetCustomFieldValue(String environmentName, String key)
{
try
{
_dataLock.EnterReadLock();

_contextCustomFields.TryGetValue(environmentName, out var customFieldValues);

if (customFieldValues != null)
{
customFieldValues.TryGetValue(key, out var field);
if (field != null)
{
return field.Value;
}
}

return null;
}
finally
{
_dataLock.ExitReadLock();
}
}

public Object GetCustomFieldType(String environmentName, String key)
{
try
{
_dataLock.EnterReadLock();

_contextCustomFields.TryGetValue(environmentName, out var customFieldValues);

if (customFieldValues != null)
{
customFieldValues.TryGetValue(key, out var field);
if (field != null)
{
return field.Type;
}
}

return null;
}
finally
{
_dataLock.ExitReadLock();
}
}

private ExperimentVariables GetVariableExperiment(string key)
{
Expand Down Expand Up @@ -854,6 +934,7 @@ private void SetData(ContextData data)
{
var index = new Dictionary<string, ExperimentVariables>();
var indexVariables = new Dictionary<string, ExperimentVariables>();
var contextCustomFields = new Dictionary<string, Dictionary<string, ContextCustomFieldValue>>();

foreach (var experiment in data.Experiments)
{
Expand All @@ -878,13 +959,51 @@ private void SetData(ContextData data)
}

index[experiment.Name] = experimentVariables;

if (experiment.CustomFieldValues == null) continue;

var experimentCustomFields = new Dictionary<string, ContextCustomFieldValue>();
foreach (var customFieldValue in experiment.CustomFieldValues)
{
var value = new ContextCustomFieldValue
{
Type = customFieldValue.Type
};

if (customFieldValue.Value != null)
{
var customValue = customFieldValue.Value;

if (customFieldValue.Type.StartsWith("json"))
{
value.Value = _variableParser.Parse(this, experiment.Name, customFieldValue.Name, customValue);
}
else if(customFieldValue.Type.StartsWith("boolean"))
{
value.Value = Convert.ToBoolean(customValue);
}
else if(customFieldValue.Type.StartsWith("number"))
{
value.Value = Convert.ToInt64(customValue);
}
else
{
value.Value = customValue;
}
}

experimentCustomFields[customFieldValue.Name] = value;
}

contextCustomFields[experiment.Name] = experimentCustomFields;
}

try
{
_dataLock.EnterWriteLock();

_index = index;
_contextCustomFields = contextCustomFields;
_indexVariables =
new DictionaryLockableAdapter<string, ExperimentVariables>(new LockableCollectionSlimLock(_dataLock),
indexVariables);
Expand Down Expand Up @@ -998,6 +1117,13 @@ public class ExperimentVariables
public Experiment Data { get; set; }
public List<Dictionary<string, object>> Variables { get; set; }
}

public class ContextCustomFieldValue
{
public String Name { get; set; }
public String Type { get; set; }
public Object Value { get; set; }
}

public class Assignment
{
Expand Down
46 changes: 46 additions & 0 deletions src/ABSmartly.Sdk/Models/CustomFieldValue.cs
@@ -0,0 +1,46 @@
using System.Diagnostics;

namespace ABSmartly.Models;

[DebuggerDisplay("{DebugView},nq")]
public class CustomFieldValue
{
public string Name { get; set; }
public string Type { get; set; }
public string Value { get; set; }

private string DebugView => $"ExperimentVariant{{name={Name}, type={Type}, value={Value}}}";

public override string ToString()
{
return DebugView;
}


#region Equality members

protected bool Equals(CustomFieldValue other)
{
return Name == other.Name &&
Type == other.Type&&
Value == other.Value;
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((CustomFieldValue)obj);
}

public override int GetHashCode()
{
unchecked
{
return ((Name?.GetHashCode() ?? 0) * 397) ^ (Type?.GetHashCode() ?? 0) ^ (Value?.GetHashCode() ?? 0);
}
}

#endregion
}
1 change: 1 addition & 0 deletions src/ABSmartly.Sdk/Models/Experiment.cs
Expand Up @@ -20,6 +20,7 @@ public class Experiment
public int FullOnVariant { get; set; }
public ExperimentApplication[] Applications { get; set; }
public ExperimentVariant[] Variants { get; set; }
public CustomFieldValue[] CustomFieldValues { get; set; }
public bool AudienceStrict { get; set; }
public string Audience { get; set; }

Expand Down
45 changes: 45 additions & 0 deletions tests/ABSmartly.Sdk.Tests/ContextTests.cs
Expand Up @@ -770,6 +770,51 @@ public void TestGetVariableKeys()

context.GetVariableKeys().Should().BeEquivalentTo(_variableExperiments);
}

[Test]
public void TestGetCustomFieldKeys()
{
var context = CreateContext(_data);

context.GetCustomFieldKeys().Should().BeEquivalentTo(new List<String> { "country", "languages", "overrides" });
}

[Test]
public void TestGetCustomFieldValues()
{
var context = CreateContext(_data);

context.GetCustomFieldValue("not_found", "not_found").Should().BeNull();
context.GetCustomFieldValue("exp_test_ab", "not_found").Should().BeNull();
context.GetCustomFieldValue("exp_test_ab", "country").Should().BeEquivalentTo("US,PT,ES,DE,FR");
context.GetCustomFieldType("exp_test_ab", "country").Should().BeEquivalentTo("string");

context.GetCustomFieldValue("exp_test_ab", "overrides").Should().BeEquivalentTo(new Dictionary<String, Object>
{
{ "123", 1 },
{ "456", 0 }
}
);
context.GetCustomFieldType("exp_test_ab", "overrides").Should().BeEquivalentTo("json");

context.GetCustomFieldValue("exp_test_ab", "languages").Should().BeNull();
context.GetCustomFieldValue("exp_test_ab", "languages").Should().BeNull();

context.GetCustomFieldValue("exp_test_abc", "overrides").Should().BeNull();
context.GetCustomFieldValue("exp_test_abc", "overrides").Should().BeNull();

context.GetCustomFieldValue("exp_test_abc", "languages").Should().BeEquivalentTo("en-US,en-GB,pt-PT,pt-BR,es-ES,es-MX");
context.GetCustomFieldType("exp_test_abc", "languages").Should().BeEquivalentTo("string");

context.GetCustomFieldValue("exp_test_no_custom_fields", "country").Should().BeNull();
context.GetCustomFieldValue("exp_test_no_custom_fields", "country").Should().BeNull();

context.GetCustomFieldValue("exp_test_no_custom_fields", "overrides").Should().BeNull();
context.GetCustomFieldValue("exp_test_no_custom_fields", "overrides").Should().BeNull();

context.GetCustomFieldValue("exp_test_no_custom_fields", "languages").Should().BeNull();
context.GetCustomFieldValue("exp_test_no_custom_fields", "languages").Should().BeNull();
}

[Test]
public void TestPeekTreatmentReturnsOverrideVariant()
Expand Down

0 comments on commit 51a219e

Please sign in to comment.