Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a129e3a
Added Optimizely Json
mnoman09 Apr 29, 2020
18857e6
Added unit tests
mnoman09 Apr 29, 2020
c988dd0
indentation fixes and small other fixes
mnoman09 Apr 30, 2020
581d95f
Merge branch 'master' into mnoman/optimizelyJson
mnoman09 Apr 30, 2020
cb48427
Added GetFeatureVariableJson support and unit test function
mnoman09 Apr 30, 2020
5735882
Added Unit tests
mnoman09 May 4, 2020
0f2e63c
default literal was not supported so Updated it to default(T)
mnoman09 May 4, 2020
439d5fa
comments resolved
mnoman09 May 5, 2020
4c316f4
Converted all Jobjects to dict and Jarray object to List
mnoman09 May 6, 2020
8e84f43
Added invalid cast exception class in optizelyException
mnoman09 May 6, 2020
96cfd82
Added addition unit tests and added support of casting dictionary to …
mnoman09 May 7, 2020
7efb98f
indentation fix
mnoman09 May 7, 2020
31a7dca
changed IsNullOrWhiteSpace into IsNullOrEmpty
mnoman09 May 7, 2020
348acf1
added a unit test
msohailhussain May 7, 2020
a6d1ddd
Refact
mnoman09 May 7, 2020
d18df1d
Merge branch 'mnoman/optimizelyJson' into mnoman/GetFeatureVariableJson
mnoman09 May 7, 2020
aa6de6b
updated header
mnoman09 May 7, 2020
a800616
Merge branch 'mnoman/optimizelyJson' into mnoman/GetFeatureVariableJson
mnoman09 May 7, 2020
8355cde
if notification value is optimizelyJson
mnoman09 May 7, 2020
4221dad
Added getAllFeature Variable support and notification type in notific…
mnoman09 May 7, 2020
a8aec1b
added errorhandler mock verifier
mnoman09 May 7, 2020
3db6dbf
Added additonal comments to explain functions further
mnoman09 May 7, 2020
2bf7ff1
Merge branch 'mnoman/optimizelyJson' into mnoman/GetFeatureVariableJson
mnoman09 May 8, 2020
beee956
setting type = subtype while parsing if its json
mnoman09 May 8, 2020
9317470
Merge branch 'mnoman/GetFeatureVariableJson' into mnoman/GetAllFeatur…
mnoman09 May 8, 2020
8e087b4
refact
mnoman09 May 11, 2020
fa0370c
Merge branch 'master' into mnoman/GetFeatureVariableJson
mnoman09 May 11, 2020
de64015
Added Unit Tests
mnoman09 May 11, 2020
1ba3885
Merge branch 'mnoman/GetFeatureVariableJson' into mnoman/GetAllFeatur…
mnoman09 May 11, 2020
34765fc
Added additional tests
mnoman09 May 12, 2020
618f6ba
Added getFeatureVariableJsonListener Test
mnoman09 May 13, 2020
1239929
Merge branch 'mnoman/GetFeatureVariableJson' into mnoman/GetAllFeatur…
mnoman09 May 13, 2020
aede814
Merge branch 'master' into mnoman/GetAllFeatureVariables
msohailhussain May 18, 2020
9dc95a1
Refact
mnoman09 May 19, 2020
5438c09
Name changed
mnoman09 May 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 148 additions & 1 deletion OptimizelySDK.Tests/OptimizelyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1788,7 +1788,7 @@ public void TestUnsupportedVariableType()
{
var featureVariableStringRandomType = Optimizely.GetFeatureVariableString("", "any_key", TestUserId);
Assert.IsNull(featureVariableStringRandomType);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove spacing.

// This is to test that only json subtype is parsing and all other will subtype will be stringify
var featureVariableStringRegexSubType = Optimizely.GetFeatureVariableString("unsupported_variabletype", "string_regex_key", TestUserId);
Assert.AreEqual(featureVariableStringRegexSubType, "^\\d+(\\.\\d+)?");
Expand Down Expand Up @@ -3191,8 +3191,155 @@ public void TestGetFeatureVariableDoubleSendsNotificationWhenUserNotBuckedIntoBo
NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.FEATURE_VARIABLE, TestUserId, new UserAttributes(), It.Is<Dictionary<string, object>>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once);
}

[Test]
public void TestGetAllFeatureVariablesSendsNotificationWhenUserBucketIntoRolloutAndVariationIsToggleOn()
{
var featureKey = "string_single_variable_feature";
var featureFlag = Config.GetFeatureFlagFromKey(featureKey);
var expectedValue = new Dictionary<string, object>() {
{ "string_variable", "cta_4" },
{ "json_var", new Dictionary<string, object>()
{
{ "int_var", 4 },
{ "string_var", "cta_4" }
}
},
{ "true_json_var", new Dictionary<string, object>()
{
{ "int_var", 5 },
{ "string_var", "cta_5" }
}
}
};
var experiment = Config.GetRolloutFromId("166661").Experiments[0];
var variation = Config.GetVariationFromKey(experiment.Key, "177775");
var decision = new FeatureDecision(experiment, variation, FeatureDecision.DECISION_SOURCE_ROLLOUT);
var userAttributes = new UserAttributes
{
{ "device_type", "iPhone" },
{ "company", "Optimizely" },
{ "location", "San Francisco" }
};

DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, TestUserId, Config, userAttributes)).Returns(decision);
NotificationCallbackMock.Setup(nc => nc.TestDecisionCallback(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<UserAttributes>(),
It.IsAny<Dictionary<string, object>>()));

var optly = Helper.CreatePrivateOptimizely();

optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why we are using reflection to instantiate optimizely instance. please explain

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is to set decisionservice object to decisionserviceMock object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it.

var optStronglyTyped = optly.GetObject() as Optimizely;

optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object);
optStronglyTyped.NotificationCenter.AddNotification(NotificationCenter.NotificationType.Decision, NotificationCallbackMock.Object.TestDecisionCallback);

var variableValues = (OptimizelyJson)optly.Invoke("GetAllFeatureVariables", featureKey, TestUserId, userAttributes);
Assert.IsTrue(TestData.CompareObjects(variableValues.ToDictionary(), expectedValue));
var decisionInfo = new Dictionary<string, object>
{
{ "featureKey", featureKey },
{ "featureEnabled", true },
{ "variableValues", expectedValue },
{ "source", FeatureDecision.DECISION_SOURCE_ROLLOUT },
{ "sourceInfo", new Dictionary<string, string>() },
};

NotificationCallbackMock.Verify(nc => nc.TestDecisionCallback(DecisionNotificationTypes.ALL_FEATURE_VARIABLE, TestUserId, userAttributes, It.Is<Dictionary<string, object>>(info => TestData.CompareObjects(info, decisionInfo))), Times.Once);
}

#endregion // Decision Listener

#region Test GetAllFeatureVariables

[Test]
public void TestGetAllFeatureVariablesReturnsNullScenarios()
{
var userAttributes = new UserAttributes
{
{ "device_type", "iPhone" },
{ "company", "Optimizely" },
{ "location", "San Francisco" }
};
var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object);

// Null Feature flag key
var result = optimizely.GetAllFeatureVariables(null, TestUserId, userAttributes);
Assert.Null(result);

LoggerMock.Verify(log => log.Log(LogLevel.WARN, "The featureKey parameter must be nonnull."), Times.Once);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nonnull or not-null @pawels-optimizely need suggestion.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cannot be null


// Null User ID
var result2 = optimizely.GetAllFeatureVariables("string_single_variable_feature", null, userAttributes);
Assert.Null(result2);

LoggerMock.Verify(log => log.Log(LogLevel.WARN, "The userId parameter must be nonnull."), Times.Once);

// Invalid featureKey
var featureKey = "InvalidFeatureKey";

var result3 = optimizely.GetAllFeatureVariables(featureKey, TestUserId, userAttributes);
Assert.Null(result3);

LoggerMock.Verify(log => log.Log(LogLevel.INFO, "No feature flag was found for key \"" + featureKey + "\"."), Times.Once);

// Null Optimizely config
var invalidOptly = new Optimizely("Random datafile", null, LoggerMock.Object);

var result4 = invalidOptly.GetAllFeatureVariables("validFeatureKey", TestUserId, userAttributes);

Assert.Null(result4);

LoggerMock.Verify(log => log.Log(LogLevel.ERROR, "Optimizely instance is not valid, failing getAllFeatureVariableValues call. type"), Times.Once);
}

[Test]
public void TestGetAllFeatureVariablesRollout()
{
var featureKey = "string_single_variable_feature";
var experiment = Config.GetRolloutFromId("166661").Experiments[0];

var userAttributes = new UserAttributes
{
{ "device_type", "iPhone" },
{ "company", "Optimizely" },
{ "location", "San Francisco" }
};

var featureFlag = Config.GetFeatureFlagFromKey(featureKey);

var decision = new FeatureDecision(experiment, null, FeatureDecision.DECISION_SOURCE_ROLLOUT);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why variation is null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To verify info log that ""User "" + TestUserId + "" was not bucketed into any variation for feature flag "" + featureKey + "". The default values are being returned.""


DecisionServiceMock.Setup(ds => ds.GetVariationForFeature(featureFlag, TestUserId, Config, userAttributes)).Returns(decision);

var optly = Helper.CreatePrivateOptimizely();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revise it.

optly.SetFieldOrProperty("DecisionService", DecisionServiceMock.Object);
optly.SetFieldOrProperty("ProjectConfigManager", ConfigManager);

var result = (OptimizelyJson)optly.Invoke("GetAllFeatureVariables", featureKey, TestUserId, userAttributes);
Assert.NotNull(result);

LoggerMock.Verify(log => log.Log(LogLevel.INFO, "User \"" + TestUserId + "\" was not bucketed into any variation for feature flag \"" + featureKey + "\". " +
"The default values are being returned."), Times.Once);
}

[Test]
public void TestGetAllFeatureVariablesSourceFeatureTest()
{
var featureKey = "double_single_variable_feature";
var expectedValue = new Dictionary<string, object>() {
{ "double_variable", 42.42}
};

var optimizely = new Optimizely(TestData.Datafile, EventDispatcherMock.Object, LoggerMock.Object, ErrorHandlerMock.Object);

var variableValues = optimizely.GetAllFeatureVariables(featureKey, TestUserId, null);
Assert.IsTrue(TestData.CompareObjects(variableValues.ToDictionary(), expectedValue));

LoggerMock.Verify(log => log.Log(LogLevel.INFO, "User \"" + TestUserId + "\" was not bucketed into any variation for feature flag \"" + featureKey + "\". " +
"The default values are being returned."), Times.Never);
}

#endregion
#region DFM Notification
[Test]
public void TestDFMNotificationWhenProjectConfigIsUpdated()
Expand Down
94 changes: 94 additions & 0 deletions OptimizelySDK/Optimizely.cs
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,100 @@ public List<string> GetEnabledFeatures(string userId, UserAttributes userAttribu
return enabledFeaturesList;
}

/// <summary>
/// Get the values of all variables in the feature.
/// </summary>
/// <param name="featureKey">The feature flag key</param>
/// <param name="userId">The user ID</param>
/// <param name="userAttributes">The user's attributes</param>
/// <returns>string | null An OptimizelyJSON instance for all variable values.</returns>
public OptimizelyJson GetAllFeatureVariables(string featureKey, string userId,
UserAttributes userAttributes = null)
{
var config = ProjectConfigManager?.GetConfig();
if (config == null)
{
Logger.Log(LogLevel.ERROR, "Optimizely instance is not valid, failing getAllFeatureVariableValues call. type");
return null;
}

if (featureKey == null)
{
Logger.Log(LogLevel.WARN, "The featureKey parameter must be nonnull.");
return null;
}
else if (userId == null)
{
Logger.Log(LogLevel.WARN, "The userId parameter must be nonnull.");
return null;
}

var featureFlag = config.GetFeatureFlagFromKey(featureKey);
if (string.IsNullOrEmpty(featureFlag.Key))
{
Logger.Log(LogLevel.INFO, "No feature flag was found for key \""+ featureKey + "\".");
return null;
}

if (!Validator.IsFeatureFlagValid(config, featureFlag))
return null;

var featureEnabled = false;
var decision = DecisionService.GetVariationForFeature(featureFlag, userId, config, userAttributes);
var variation = decision.Variation;

if (variation != null)
{
featureEnabled = variation.FeatureEnabled.GetValueOrDefault();
}
else
{
Logger.Log(LogLevel.INFO, "User \""+ userId + "\" was not bucketed into any variation for feature flag \""+ featureKey + "\". " +
"The default values are being returned.");
}

var valuesMap = new Dictionary<string, object>();
foreach (var featureVariable in featureFlag.Variables)
{
string variableValue = featureVariable.DefaultValue;
if (featureEnabled)
{
var featureVariableUsageInstance = variation.GetFeatureVariableUsageFromId(featureVariable.Id);
if (featureVariableUsageInstance != null)
{
variableValue = featureVariableUsageInstance.Value;
}
}

var typeCastedValue = GetTypeCastedVariableValue(variableValue, featureVariable.Type);

if (typeCastedValue is OptimizelyJson)
typeCastedValue = ((OptimizelyJson)typeCastedValue).ToDictionary();

valuesMap.Add(featureVariable.Key, typeCastedValue);
}
var sourceInfo = new Dictionary<string, string>();
if (decision?.Source == FeatureDecision.DECISION_SOURCE_FEATURE_TEST)
{
sourceInfo["experimentKey"] = decision.Experiment.Key;
sourceInfo["variationKey"] = decision.Variation.Key;
}

var decisionInfo = new Dictionary<string, object>
{
{ "featureKey", featureKey },
{ "featureEnabled", featureEnabled },
{ "variableValues", valuesMap },
{ "source", decision?.Source },
{ "sourceInfo", sourceInfo },
};

NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision, DecisionNotificationTypes.ALL_FEATURE_VARIABLE, userId,
userAttributes ?? new UserAttributes(), decisionInfo);

return new OptimizelyJson(valuesMap, ErrorHandler, Logger);
}

/// <summary>
/// Get OptimizelyConfig containing experiments and features map
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions OptimizelySDK/Utils/DecisionInfoTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ public static class DecisionNotificationTypes
public const string FEATURE = "feature";
public const string FEATURE_TEST = "feature-test";
public const string FEATURE_VARIABLE = "feature-variable";
public const string ALL_FEATURE_VARIABLE = "all-feature-variables";
}
}