Skip to content

Commit c0dc13c

Browse files
mfahadahmedmikeproeng37
authored andcommitted
feat(api): Decision Notification Listener for activate and getVariation APIs. (#148)
1 parent e17424f commit c0dc13c

File tree

9 files changed

+208
-13
lines changed

9 files changed

+208
-13
lines changed

OptimizelySDK.Net35/OptimizelySDK.Net35.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,11 @@
178178
<Compile Include="..\OptimizelySDK\Utils\ConditionParser.cs">
179179
<Link>Utils\ConditionParser.cs</Link>
180180
</Compile>
181-
<Compile Include="..\OptimizelySDK\Utils\AttributeMatchTypes.cs">
181+
<Compile Include="..\OptimizelySDK\Utils\AttributeMatchTypes.cs">
182182
<Link>Utils\AttributeMatchTypes.cs</Link>
183+
</Compile>
184+
<Compile Include="..\OptimizelySDK\Utils\DecisionInfoTypes.cs">
185+
<Link>Utils\DecisionInfoTypes.cs</Link>
183186
</Compile>
184187
<Compile Include="Properties\AssemblyInfo.cs" />
185188
<Compile Include="..\OptimizelySDK\Bucketing\Bucketer.cs">

OptimizelySDK.Net40/OptimizelySDK.Net40.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@
181181
</Compile>
182182
<Compile Include="..\OptimizelySDK\Utils\AttributeMatchTypes.cs">
183183
<Link>Utils\AttributeMatchTypes.cs</Link>
184+
</Compile>
185+
<Compile Include="..\OptimizelySDK\Utils\DecisionInfoTypes.cs">
186+
<Link>Utils\DecisionInfoTypes.cs</Link>
184187
</Compile>
185188
<Compile Include="Properties\AssemblyInfo.cs" />
186189
<Compile Include="..\OptimizelySDK\Bucketing\Bucketer.cs">

OptimizelySDK.NetStandard16/OptimizelySDK.NetStandard16.csproj

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@
5757
<Compile Include=".\Properties\AssemblyInfo.cs" />
5858
<Compile Include="..\OptimizelySDK\Utils\ConfigParser.cs" />
5959
<Compile Include="..\OptimizelySDK\Utils\Schema.cs" />
60-
<Compile Include="..\OptimizelySDK\Utils\ControlAttributes.cs" />
61-
<Compile Include="..\OptimizelySDK\Utils\ExceptionExtensions.cs" />
62-
<Compile Include="..\OptimizelySDK\Utils\ExperimentUtils.cs" />
63-
<Compile Include="..\OptimizelySDK\Utils\ConditionParser.cs" />
64-
<Compile Include="..\OptimizelySDK\Utils\AttributeMatchTypes.cs" />
60+
<Compile Include="..\OptimizelySDK\Utils\ControlAttributes.cs" />
61+
<Compile Include="..\OptimizelySDK\Utils\ExceptionExtensions.cs" />
62+
<Compile Include="..\OptimizelySDK\Utils\ExperimentUtils.cs" />
63+
<Compile Include="..\OptimizelySDK\Utils\ConditionParser.cs" />
64+
<Compile Include="..\OptimizelySDK\Utils\AttributeMatchTypes.cs" />
65+
<Compile Include="..\OptimizelySDK\Utils\DecisionInfoTypes.cs" />
6566
<Compile Include="..\OptimizelySDK\Bucketing\Bucketer.cs" />
6667
<Compile Include="..\OptimizelySDK\Bucketing\Decision.cs" />
6768
<Compile Include="..\OptimizelySDK\Bucketing\DecisionService.cs" />

OptimizelySDK.Tests/NotificationTests/NotificationCenterTests.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017, Optimizely
2+
* Copyright 2017, 2019, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -236,6 +236,10 @@ public virtual void TestTrackCallback(string eventKey, string userId, UserAttrib
236236
public virtual void TestAnotherTrackCallback(string eventKey, string userId, UserAttributes userAttributes,
237237
EventTags eventTags, LogEvent logEvent) {
238238
}
239+
240+
public virtual void TestDecisionCallback(string type, string userId, UserAttributes userAttributes,
241+
Dictionary<string, object> decisionInfo) {
242+
}
239243
}
240244
#endregion // Test Notification callbacks class.
241245
}

OptimizelySDK.Tests/OptimizelyTest.cs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2059,6 +2059,132 @@ public void TestTrackListener(UserAttributes userAttributes, EventTags eventTags
20592059
NotificationCallbackMock.Verify(nc => nc.TestAnotherTrackCallback(eventKey, TestUserId, userAttributes, eventTags, logEvent), Times.Exactly(1));
20602060
}
20612061

2062+
#region Decision Listener
2063+
2064+
[Test]
2065+
public void TestActivateSendsDecisionNotificationWithActualVariationKey()
2066+
{
2067+
var experimentKey = "group_experiment_1";
2068+
var variationKey = "group_exp_1_var_1";
2069+
var experiment = Config.GetExperimentFromKey(experimentKey);
2070+
var variation = Config.GetVariationFromKey(experimentKey, variationKey);
2071+
var userAttributes = new UserAttributes
2072+
{
2073+
{ "device_type", "iPhone" },
2074+
{ "company", "Optimizely" },
2075+
{ "location", "San Francisco" }
2076+
};
2077+
2078+
// Mocking objects.
2079+
NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny<string>(), It.IsAny<string>(),
2080+
It.IsAny<UserAttributes>(), It.IsAny<Dictionary<string, object>>()));
2081+
DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, TestUserId, userAttributes)).Returns(variation);
2082+
2083+
var optly = Helper.CreatePrivateOptimizely();
2084+
var optStronglyTyped = optly.GetObject() as Optimizely;
2085+
2086+
optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback);
2087+
optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object);
2088+
2089+
optly.Invoke("Activate", experimentKey, TestUserId, userAttributes);
2090+
var decisionInfo = new Dictionary<string, object>
2091+
{
2092+
{ "experimentKey", experimentKey },
2093+
{ "variationKey", variationKey },
2094+
};
2095+
2096+
NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionInfoTypes.EXPERIMENT, TestUserId, userAttributes, decisionInfo), Times.Once);
2097+
}
2098+
2099+
[Test]
2100+
public void TestActivateSendsDecisionNotificationWithNullVariationKey()
2101+
{
2102+
var experimentKey = "group_experiment_1";
2103+
var experiment = Config.GetExperimentFromKey(experimentKey);
2104+
2105+
NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny<string>(), It.IsAny<string>(),
2106+
It.IsAny<UserAttributes>(), It.IsAny<Dictionary<string, object>>()));
2107+
DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, TestUserId, null)).Returns<Variation>(null);
2108+
2109+
var optly = Helper.CreatePrivateOptimizely();
2110+
var optStronglyTyped = optly.GetObject() as Optimizely;
2111+
2112+
optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback);
2113+
optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object);
2114+
2115+
optly.Invoke("Activate", experimentKey, TestUserId, null);
2116+
var decisionInfo = new Dictionary<string, object>
2117+
{
2118+
{ "experimentKey", experimentKey },
2119+
{ "variationKey", null },
2120+
};
2121+
2122+
NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionInfoTypes.EXPERIMENT, TestUserId, new UserAttributes(), decisionInfo), Times.Once);
2123+
}
2124+
2125+
[Test]
2126+
public void TestGetVariationSendsDecisionNotificationWithActualVariationKey()
2127+
{
2128+
var experimentKey = "group_experiment_1";
2129+
var variationKey = "group_exp_1_var_1";
2130+
var experiment = Config.GetExperimentFromKey(experimentKey);
2131+
var variation = Config.GetVariationFromKey(experimentKey, variationKey);
2132+
var userAttributes = new UserAttributes
2133+
{
2134+
{ "device_type", "iPhone" },
2135+
{ "company", "Optimizely" },
2136+
{ "location", "San Francisco" }
2137+
};
2138+
2139+
// Mocking objects.
2140+
NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny<string>(), It.IsAny<string>(),
2141+
It.IsAny<UserAttributes>(), It.IsAny<Dictionary<string, object>>()));
2142+
DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, TestUserId, userAttributes)).Returns(variation);
2143+
2144+
var optly = Helper.CreatePrivateOptimizely();
2145+
var optStronglyTyped = optly.GetObject() as Optimizely;
2146+
2147+
optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback);
2148+
optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object);
2149+
2150+
optly.Invoke("GetVariation", experimentKey, TestUserId, userAttributes);
2151+
var decisionInfo = new Dictionary<string, object>
2152+
{
2153+
{ "experimentKey", experimentKey },
2154+
{ "variationKey", variationKey },
2155+
};
2156+
2157+
NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionInfoTypes.EXPERIMENT, TestUserId, userAttributes, decisionInfo), Times.Once);
2158+
}
2159+
2160+
[Test]
2161+
public void TestGetVariationSendsDecisionNotificationWithNullVariationKey()
2162+
{
2163+
var experimentKey = "group_experiment_1";
2164+
var experiment = Config.GetExperimentFromKey(experimentKey);
2165+
2166+
NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny<string>(), It.IsAny<string>(),
2167+
It.IsAny<UserAttributes>(), It.IsAny<Dictionary<string, object>>()));
2168+
DecisionServiceMock.Setup(ds => ds.GetVariation(experiment, TestUserId, null)).Returns<Variation>(null);
2169+
2170+
var optly = Helper.CreatePrivateOptimizely();
2171+
var optStronglyTyped = optly.GetObject() as Optimizely;
2172+
2173+
optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback);
2174+
optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object);
2175+
2176+
optly.Invoke("GetVariation", experimentKey, TestUserId, null);
2177+
var decisionInfo = new Dictionary<string, object>
2178+
{
2179+
{ "experimentKey", experimentKey },
2180+
{ "variationKey", null },
2181+
};
2182+
2183+
NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionInfoTypes.EXPERIMENT, TestUserId, new UserAttributes(), decisionInfo), Times.Once);
2184+
}
2185+
2186+
#endregion // Decision Listener
2187+
20622188
#endregion // Test NotificationCenter
20632189

20642190
#region Test GetEnabledFeatures

OptimizelySDK/Notifications/NotificationCenter.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017, Optimizely
2+
* Copyright 2017, 2019, Optimizely
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,8 +33,9 @@ public class NotificationCenter
3333
/// </summary>
3434
public enum NotificationType
3535
{
36-
Activate, // Activate called.
37-
Track
36+
Activate, // Activate called.
37+
Track, // Track called.
38+
Decision // A decision is made in the system. i.e. user activation, feature access or feature-variable value retrieval.
3839
};
3940

4041
/// <summary>
@@ -59,6 +60,15 @@ public delegate void ActivateCallback(Experiment experiment, string userId, User
5960
public delegate void TrackCallback(string eventKey, string userId, UserAttributes userAttributes, EventTags eventTags,
6061
LogEvent logEvent);
6162

63+
/// <summary>
64+
/// Delegate for decision notifications.
65+
/// </summary>
66+
/// <param name="type">Decision-Info type</param>
67+
/// <param name="userId">The user identifier</param>
68+
/// <param name="userAttributes">Associative array of attributes for the user</param>
69+
/// <param name="decisionInfo">Dictionary containing decision information</param>
70+
public delegate void DecisionCallback(string type, string userId, UserAttributes userAttributes, Dictionary<string, object> decisionInfo);
71+
6272
private ILogger Logger;
6373

6474
// Notification Id represeting number of notifications.
@@ -129,6 +139,20 @@ public int AddNotification(NotificationType notificationType, TrackCallback trac
129139
return AddNotification(notificationType, (object)trackCallback);
130140
}
131141

142+
/// <summary>
143+
/// Add a notification callback of decision type to the notification center.
144+
/// </summary>
145+
/// <param name="notificationType">Notification type</param>
146+
/// <param name="decisionCallback">Callback function to call when event gets triggered</param>
147+
/// <returns>int | 0 for invalid notification type, -1 for adding existing notification
148+
/// or the notification id of newly added notification.</returns>
149+
public int AddNotification(NotificationType notificationType, DecisionCallback decisionCallback)
150+
{
151+
if (!IsNotificationTypeValid(notificationType, NotificationType.Decision))
152+
return 0;
153+
154+
return AddNotification(notificationType, (object)decisionCallback);
155+
}
132156

133157
/// <summary>
134158
/// Validate notification type.

OptimizelySDK/Optimizely.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public Variation Activate(string experimentKey, string userId, UserAttributes us
189189
return null;
190190
}
191191

192-
var variation = DecisionService.GetVariation(experiment, userId, userAttributes);
192+
var variation = GetVariation(experimentKey, userId, userAttributes);
193193

194194
if (variation == null || variation.Key == null)
195195
{
@@ -294,13 +294,23 @@ public Variation GetVariation(string experimentKey, string userId, UserAttribute
294294
};
295295

296296
if (!ValidateStringInputs(inputValues))
297-
return null;
297+
return null;
298298

299299
Experiment experiment = Config.GetExperimentFromKey(experimentKey);
300300
if (experiment.Key == null)
301301
return null;
302302

303-
return DecisionService.GetVariation(experiment, userId, userAttributes);
303+
var variation = DecisionService.GetVariation(experiment, userId, userAttributes);
304+
var decisionInfo = new Dictionary<string, object>
305+
{
306+
{ "experimentKey", experimentKey },
307+
{ "variationKey", variation?.Key },
308+
};
309+
310+
userAttributes = userAttributes ?? new UserAttributes();
311+
NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision, DecisionInfoTypes.EXPERIMENT, userId,
312+
userAttributes, decisionInfo);
313+
return variation;
304314
}
305315

306316
/// <summary>

OptimizelySDK/OptimizelySDK.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
<Compile Include="Bucketing\UserProfile.cs" />
110110
<Compile Include="Utils\AttributeMatchTypes.cs" />
111111
<Compile Include="Utils\ConditionParser.cs" />
112+
<Compile Include="Utils\DecisionInfoTypes.cs" />
112113
<Compile Include="Utils\EventTagUtils.cs" />
113114
<Compile Include="Bucketing\UserProfileUtil.cs" />
114115
<Compile Include="Utils\ExperimentUtils.cs" />
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2019, 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+
namespace OptimizelySDK.Utils
18+
{
19+
public static class DecisionInfoTypes
20+
{
21+
public const string EXPERIMENT = "experiment";
22+
}
23+
}

0 commit comments

Comments
 (0)