Skip to content

Commit dd69bea

Browse files
[FSSDK-11138] update project config (#390)
1 parent e99b75a commit dd69bea

File tree

12 files changed

+178
-2
lines changed

12 files changed

+178
-2
lines changed

OptimizelySDK.Net35/OptimizelySDK.Net35.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
<Compile Include="..\OptimizelySDK\Entity\Experiment.cs">
8989
<Link>Entity\Experiment.cs</Link>
9090
</Compile>
91+
<Compile Include="..\OptimizelySDK\Entity\Cmab.cs">
92+
<Link>Entity\Cmab.cs</Link>
93+
</Compile>
9194
<Compile Include="..\OptimizelySDK\Entity\Holdout.cs">
9295
<Link>Entity\Holdout.cs</Link>
9396
</Compile>

OptimizelySDK.Net40/OptimizelySDK.Net40.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@
9090
<Compile Include="..\OptimizelySDK\Entity\Experiment.cs">
9191
<Link>Entity\Experiment.cs</Link>
9292
</Compile>
93+
<Compile Include="..\OptimizelySDK\Entity\Cmab.cs">
94+
<Link>Entity\Cmab.cs</Link>
95+
</Compile>
9396
<Compile Include="..\OptimizelySDK\Entity\Holdout.cs">
9497
<Link>Entity\Holdout.cs</Link>
9598
</Compile>

OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<Compile Include="..\OptimizelySDK\Entity\Event.cs" />
2727
<Compile Include="..\OptimizelySDK\Entity\EventTags.cs" />
2828
<Compile Include="..\OptimizelySDK\Entity\Experiment.cs" />
29+
<Compile Include="..\OptimizelySDK\Entity\Cmab.cs" />
2930
<Compile Include="..\OptimizelySDK\Entity\Holdout.cs" />
3031
<Compile Include="..\OptimizelySDK\Entity\ExperimentCore.cs" />
3132
<Compile Include="..\OptimizelySDK\Entity\FeatureDecision.cs" />

OptimizelySDK.NetStandard20/OptimizelySDK.NetStandard20.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@
178178
<Compile Include="..\OptimizelySDK\Entity\Experiment.cs">
179179
<Link>Entity\Experiment.cs</Link>
180180
</Compile>
181+
<Compile Include="..\OptimizelySDK\Entity\Cmab.cs">
182+
<Link>Entity\Cmab.cs</Link>
183+
</Compile>
181184
<Compile Include="..\OptimizelySDK\Entity\Holdout.cs">
182185
<Link>Entity\Holdout.cs</Link>
183186
</Compile>

OptimizelySDK.Tests/ProjectConfigTest.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,5 +1455,40 @@ public void TestMissingHoldoutsField_BackwardCompatibility()
14551455
}
14561456

14571457
#endregion
1458+
1459+
[Test]
1460+
public void TestCmabFieldPopulation()
1461+
{
1462+
1463+
var datafileJson = JObject.Parse(TestData.Datafile);
1464+
var experiments = (JArray)datafileJson["experiments"];
1465+
1466+
if (experiments.Count > 0)
1467+
{
1468+
var firstExperiment = (JObject)experiments[0];
1469+
1470+
firstExperiment["cmab"] = new JObject
1471+
{
1472+
["attributeIds"] = new JArray { "7723280020", "7723348204" },
1473+
["trafficAllocation"] = 4000
1474+
};
1475+
1476+
firstExperiment["trafficAllocation"] = new JArray();
1477+
}
1478+
1479+
var modifiedDatafile = datafileJson.ToString();
1480+
var projectConfig = DatafileProjectConfig.Create(modifiedDatafile, LoggerMock.Object, ErrorHandlerMock.Object);
1481+
var experimentWithCmab = projectConfig.GetExperimentFromKey("test_experiment");
1482+
1483+
Assert.IsNotNull(experimentWithCmab.Cmab);
1484+
Assert.AreEqual(2, experimentWithCmab.Cmab.AttributeIds.Count);
1485+
Assert.Contains("7723280020", experimentWithCmab.Cmab.AttributeIds);
1486+
Assert.Contains("7723348204", experimentWithCmab.Cmab.AttributeIds);
1487+
Assert.AreEqual(4000, experimentWithCmab.Cmab.TrafficAllocation);
1488+
1489+
var experimentWithoutCmab = projectConfig.GetExperimentFromKey("paused_experiment");
1490+
1491+
Assert.IsNull(experimentWithoutCmab.Cmab);
1492+
}
14581493
}
14591494
}

OptimizelySDK/Config/DatafileProjectConfig.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@ private Dictionary<string, Dictionary<string, Variation>> _VariationIdMap
195195

196196
public Dictionary<string, Attribute> AttributeKeyMap => _AttributeKeyMap;
197197

198+
/// <summary>
199+
/// Associative array of attribute ID to Attribute(s) in the datafile
200+
/// </summary>
201+
private Dictionary<string, Attribute> _AttributeIdMap;
202+
203+
public Dictionary<string, Attribute> AttributeIdMap => _AttributeIdMap;
204+
198205
/// <summary>
199206
/// Associative array of audience ID to Audience(s) in the datafile
200207
/// </summary>
@@ -332,6 +339,8 @@ private void Initialize()
332339
true);
333340
_AttributeKeyMap = ConfigParser<Attribute>.GenerateMap(Attributes,
334341
a => a.Key, true);
342+
_AttributeIdMap = ConfigParser<Attribute>.GenerateMap(Attributes,
343+
a => a.Id, true);
335344
_AudienceIdMap = ConfigParser<Audience>.GenerateMap(Audiences,
336345
a => a.Id.ToString(), true);
337346
_FeatureKeyMap = ConfigParser<FeatureFlag>.GenerateMap(FeatureFlags,
@@ -653,6 +662,25 @@ public Attribute GetAttribute(string attributeKey)
653662
return new Attribute();
654663
}
655664

665+
/// <summary>
666+
/// Get the Attribute from the ID
667+
/// </summary>
668+
/// <param name="attributeId">ID of the Attribute</param>
669+
/// <returns>Attribute Entity corresponding to the ID or a dummy entity if ID is invalid</returns>
670+
public Attribute GetAttributeById(string attributeId)
671+
{
672+
if (_AttributeIdMap.ContainsKey(attributeId))
673+
{
674+
return _AttributeIdMap[attributeId];
675+
}
676+
677+
var message = $@"Attribute ID ""{attributeId}"" is not in datafile.";
678+
Logger.Log(LogLevel.ERROR, message);
679+
ErrorHandler.HandleError(
680+
new InvalidAttributeException("Provided attribute is not in datafile."));
681+
return new Attribute();
682+
}
683+
656684
/// <summary>
657685
/// Get the Variation from the keys
658686
/// </summary>

OptimizelySDK/Entity/Cmab.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2025, Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System.Collections.Generic;
18+
using Newtonsoft.Json;
19+
20+
namespace OptimizelySDK.Entity
21+
{
22+
/// <summary>
23+
/// Class representing CMAB (Contextual Multi-Armed Bandit) configuration for experiments.
24+
/// </summary>
25+
public class Cmab
26+
{
27+
/// <summary>
28+
/// List of attribute IDs that are relevant for CMAB decision making.
29+
/// These attributes will be used to filter user attributes when making CMAB requests.
30+
/// </summary>
31+
[JsonProperty("attributeIds")]
32+
public List<string> AttributeIds { get; set; }
33+
34+
/// <summary>
35+
/// Traffic allocation value for CMAB experiments.
36+
/// Determines what portion of traffic should be allocated to CMAB decision making.
37+
/// </summary>
38+
[JsonProperty("trafficAllocation")]
39+
public int? TrafficAllocation { get; set; }
40+
41+
/// <summary>
42+
/// Initializes a new instance of the Cmab class with specified values.
43+
/// </summary>
44+
/// <param name="attributeIds">List of attribute IDs for CMAB</param>
45+
/// <param name="trafficAllocation">Traffic allocation value</param>
46+
public Cmab(List<string> attributeIds, int? trafficAllocation = null)
47+
{
48+
AttributeIds = attributeIds ?? new List<string>();
49+
TrafficAllocation = trafficAllocation;
50+
}
51+
52+
/// <summary>
53+
/// Returns a string representation of the CMAB configuration.
54+
/// </summary>
55+
/// <returns>String representation</returns>
56+
public override string ToString()
57+
{
58+
var attributeList = AttributeIds ?? new List<string>();
59+
return string.Format("Cmab{{AttributeIds=[{0}], TrafficAllocation={1}}}",
60+
string.Join(", ", attributeList.ToArray()), TrafficAllocation);
61+
}
62+
}
63+
}

OptimizelySDK/Entity/Experiment.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
using System.Collections.Generic;
18+
using Newtonsoft.Json;
1819

1920
namespace OptimizelySDK.Entity
2021
{
@@ -48,6 +49,12 @@ public class Experiment : ExperimentCore
4849
public bool IsInMutexGroup =>
4950
!string.IsNullOrEmpty(GroupPolicy) && GroupPolicy == MUTEX_GROUP_POLICY;
5051

52+
/// <summary>
53+
/// CMAB (Contextual Multi-Armed Bandit) configuration for the experiment.
54+
/// </summary>
55+
[JsonProperty("cmab")]
56+
public Cmab Cmab { get; set; }
57+
5158
/// <summary>
5259
/// Determin if user is forced variation of experiment
5360
/// </summary>

OptimizelySDK/OptimizelySDK.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
<Compile Include="Entity\Event.cs"/>
8585
<Compile Include="Entity\EventTags.cs"/>
8686
<Compile Include="Entity\Experiment.cs"/>
87+
<Compile Include="Entity\Cmab.cs"/>
8788
<Compile Include="Entity\ExperimentCore.cs"/>
8889
<Compile Include="Entity\Holdout.cs"/>
8990
<Compile Include="Entity\FeatureDecision.cs"/>

OptimizelySDK/ProjectConfig.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ public interface ProjectConfig
113113
/// </summary>
114114
Dictionary<string, Attribute> AttributeKeyMap { get; }
115115

116+
/// <summary>
117+
/// Associative array of attribute ID to Attribute(s) in the datafile
118+
/// </summary>
119+
Dictionary<string, Attribute> AttributeIdMap { get; }
120+
116121
/// <summary>
117122
/// Associative array of audience ID to Audience(s) in the datafile
118123
/// </summary>
@@ -234,6 +239,13 @@ public interface ProjectConfig
234239
/// <returns>Attribute Entity corresponding to the key or a dummy entity if key is invalid</returns>
235240
Attribute GetAttribute(string attributeKey);
236241

242+
/// <summary>
243+
/// Get the Attribute from the ID
244+
/// </summary>
245+
/// <param name="attributeId">ID of the Attribute</param>
246+
/// <returns>Attribute Entity corresponding to the ID or a dummy entity if ID is invalid</returns>
247+
Attribute GetAttributeById(string attributeId);
248+
237249
/// <summary>
238250
/// Get the Variation from the keys
239251
/// </summary>

0 commit comments

Comments
 (0)