diff --git a/OptimizelySDK.sln.DotSettings b/OptimizelySDK.sln.DotSettings
index 3ccf7ffc..8ee6e5a4 100644
--- a/OptimizelySDK.sln.DotSettings
+++ b/OptimizelySDK.sln.DotSettings
@@ -43,10 +43,15 @@
<Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"><ElementKinds><Kind Name="ENUM_MEMBER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb" /></Policy></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Interfaces"><ElementKinds><Kind Name="INTERFACE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /></Policy>
True
True
True
True
+ True
True
True
True
diff --git a/OptimizelySDK/Bucketing/DecisionService.cs b/OptimizelySDK/Bucketing/DecisionService.cs
index c820fefc..4dba482c 100644
--- a/OptimizelySDK/Bucketing/DecisionService.cs
+++ b/OptimizelySDK/Bucketing/DecisionService.cs
@@ -43,7 +43,7 @@ public class DecisionService
private Bucketer Bucketer;
private IErrorHandler ErrorHandler;
private UserProfileService UserProfileService;
- private ILogger Logger;
+ private static ILogger Logger;
///
/// Associative array of user IDs to an associative array
@@ -85,9 +85,9 @@ public DecisionService(Bucketer bucketer, IErrorHandler errorHandler,
///
/// Get a Variation of an Experiment for a user to be allocated into.
///
- /// The Experiment the user will be bucketed into.
- /// Optimizely user context.
- /// Project config.
+ /// The Experiment the user will be bucketed into.
+ /// Optimizely user context.
+ /// Project config.
/// The Variation the user is allocated into.
public virtual Result GetVariation(Experiment experiment,
OptimizelyUserContext user,
@@ -100,11 +100,11 @@ ProjectConfig config
///
/// Get a Variation of an Experiment for a user to be allocated into.
///
- /// The Experiment the user will be bucketed into.
- /// optimizely user context.
- /// Project Config.
- /// An array of decision options.
- /// The Variation the user is allocated into.
+ /// The Experiment the user will be bucketed into.
+ /// Optimizely user context.
+ /// Project Config.
+ /// An array of decision options.
+ ///
public virtual Result GetVariation(Experiment experiment,
OptimizelyUserContext user,
ProjectConfig config,
@@ -112,12 +112,60 @@ OptimizelyDecideOption[] options
)
{
var reasons = new DecisionReasons();
- var userId = user.GetUserId();
+
+ var ignoreUps = options.Contains(OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE);
+ UserProfileTracker userProfileTracker = null;
+
+ if (UserProfileService != null && !ignoreUps)
+ {
+ var userProfile = GetUserProfile(user.GetUserId(), reasons);
+ userProfileTracker = new UserProfileTracker(userProfile, false);
+ }
+
+ var response = GetVariation(experiment, user, config, options, userProfileTracker,
+ reasons);
+
+ if (UserProfileService != null && !ignoreUps &&
+ userProfileTracker?.ProfileUpdated == true)
+ {
+ SaveUserProfile(userProfileTracker.UserProfile);
+ }
+
+ return response;
+ }
+
+ ///
+ /// Get a Variation of an Experiment for a user to be allocated into.
+ ///
+ /// The Experiment the user will be bucketed into.
+ /// Optimizely user context.
+ /// Project Config.
+ /// An array of decision options.
+ /// A UserProfileTracker object.
+ /// Set of reasons for the decision.
+ /// The Variation the user is allocated into.
+ public virtual Result GetVariation(Experiment experiment,
+ OptimizelyUserContext user,
+ ProjectConfig config,
+ OptimizelyDecideOption[] options,
+ UserProfileTracker userProfileTracker,
+ DecisionReasons reasons = null
+ )
+ {
+ if (reasons == null)
+ {
+ reasons = new DecisionReasons();
+ }
+
if (!ExperimentUtils.IsExperimentActive(experiment, Logger))
{
+ var message = reasons.AddInfo($"Experiment {experiment.Key} is not running.");
+ Logger.Log(LogLevel.INFO, message);
return Result.NullResult(reasons);
}
+ var userId = user.GetUserId();
+
// check if a forced variation is set
var decisionVariationResult = GetForcedVariation(experiment.Key, userId, config);
reasons += decisionVariationResult.DecisionReasons;
@@ -137,76 +185,41 @@ OptimizelyDecideOption[] options
return decisionVariationResult;
}
- // fetch the user profile map from the user profile service
- var ignoreUPS = Array.Exists(options,
- option => option == OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE);
-
- UserProfile userProfile = null;
- if (!ignoreUPS && UserProfileService != null)
+ if (userProfileTracker != null)
{
- try
- {
- var userProfileMap = UserProfileService.Lookup(user.GetUserId());
- if (userProfileMap != null &&
- UserProfileUtil.IsValidUserProfileMap(userProfileMap))
- {
- userProfile = UserProfileUtil.ConvertMapToUserProfile(userProfileMap);
- decisionVariationResult =
- GetStoredVariation(experiment, userProfile, config);
- reasons += decisionVariationResult.DecisionReasons;
- if (decisionVariationResult.ResultObject != null)
- {
- return decisionVariationResult.SetReasons(reasons);
- }
- }
- else if (userProfileMap == null)
- {
- Logger.Log(LogLevel.INFO,
- reasons.AddInfo(
- "We were unable to get a user profile map from the UserProfileService."));
- }
- else
- {
- Logger.Log(LogLevel.ERROR,
- reasons.AddInfo("The UserProfileService returned an invalid map."));
- }
- }
- catch (Exception exception)
+ decisionVariationResult =
+ GetStoredVariation(experiment, userProfileTracker.UserProfile, config);
+ reasons += decisionVariationResult.DecisionReasons;
+ variation = decisionVariationResult.ResultObject;
+ if (variation != null)
{
- Logger.Log(LogLevel.ERROR, reasons.AddInfo(exception.Message));
- ErrorHandler.HandleError(
- new Exceptions.OptimizelyRuntimeException(exception.Message));
+ return decisionVariationResult;
}
}
- var filteredAttributes = user.GetAttributes();
- var doesUserMeetAudienceConditionsResult =
- ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, user,
- LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger);
- reasons += doesUserMeetAudienceConditionsResult.DecisionReasons;
- if (doesUserMeetAudienceConditionsResult.ResultObject)
+ var decisionMeetAudience = ExperimentUtils.DoesUserMeetAudienceConditions(config,
+ experiment, user,
+ LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger);
+ reasons += decisionMeetAudience.DecisionReasons;
+ if (decisionMeetAudience.ResultObject)
{
- // Get Bucketing ID from user attributes.
- var bucketingIdResult = GetBucketingId(userId, filteredAttributes);
+ var bucketingIdResult = GetBucketingId(userId, user.GetAttributes());
reasons += bucketingIdResult.DecisionReasons;
decisionVariationResult = Bucketer.Bucket(config, experiment,
bucketingIdResult.ResultObject, userId);
reasons += decisionVariationResult.DecisionReasons;
+ variation = decisionVariationResult.ResultObject;
- if (decisionVariationResult.ResultObject?.Key != null)
+ if (variation != null)
{
- if (UserProfileService != null && !ignoreUPS)
+ if (userProfileTracker != null)
{
- var bucketerUserProfile = userProfile ??
- new UserProfile(userId,
- new Dictionary());
- SaveVariation(experiment, decisionVariationResult.ResultObject,
- bucketerUserProfile);
+ userProfileTracker.UpdateUserProfile(experiment, variation);
}
else
{
- Logger.Log(LogLevel.INFO,
+ Logger.Log(LogLevel.DEBUG,
"This decision will not be saved since the UserProfileService is null.");
}
}
@@ -720,18 +733,6 @@ public virtual Result GetVariationForFeature(FeatureFlag featur
new OptimizelyDecideOption[] { });
}
- private class UserProfileTracker
- {
- public UserProfile UserProfile { get; set; }
- public bool ProfileUpdated { get; set; }
-
- public UserProfileTracker(UserProfile userProfile, bool profileUpdated)
- {
- UserProfile = userProfile;
- ProfileUpdated = profileUpdated;
- }
- }
-
void SaveUserProfile(UserProfile userProfile)
{
if (UserProfileService == null)
@@ -791,6 +792,40 @@ private UserProfile GetUserProfile(String userId, DecisionReasons reasons)
return userProfile;
}
+ public class UserProfileTracker
+ {
+ public UserProfile UserProfile { get; set; }
+ public bool ProfileUpdated { get; set; }
+
+ public UserProfileTracker(UserProfile userProfile, bool profileUpdated)
+ {
+ UserProfile = userProfile;
+ ProfileUpdated = profileUpdated;
+ }
+
+ public void UpdateUserProfile(Experiment experiment, Variation variation)
+ {
+ var experimentId = experiment.Id;
+ var variationId = variation.Id;
+ Decision decision;
+ if (UserProfile.ExperimentBucketMap.ContainsKey(experimentId))
+ {
+ decision = UserProfile.ExperimentBucketMap[experimentId];
+ decision.VariationId = variationId;
+ }
+ else
+ {
+ decision = new Decision(variationId);
+ }
+
+ UserProfile.ExperimentBucketMap[experimentId] = decision;
+ ProfileUpdated = true;
+
+ Logger.Log(LogLevel.INFO,
+ $"Updated variation \"{variationId}\" of experiment \"{experimentId}\" for user \"{UserProfile.UserId}\".");
+ }
+ }
+
public virtual List> GetVariationsForFeatureList(
List featureFlags,
OptimizelyUserContext user,
@@ -834,22 +869,23 @@ OptimizelyDecideOption[] options
decisionResult = GetVariationForFeatureRollout(featureFlag, user, projectConfig);
reasons += decisionResult.DecisionReasons;
- if (decisionResult.ResultObject != null)
+ if (decisionResult.ResultObject == null)
+ {
+ Logger.Log(LogLevel.INFO,
+ reasons.AddInfo(
+ $"The user \"{userId}\" is not bucketed into a rollout for feature flag \"{featureFlag.Key}\"."));
+ decisions.Add(Result.NewResult(
+ new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT),
+ reasons));
+ }
+ else
{
Logger.Log(LogLevel.INFO,
reasons.AddInfo(
$"The user \"{userId}\" is bucketed into a rollout for feature flag \"{featureFlag.Key}\"."));
decisions.Add(
Result.NewResult(decisionResult.ResultObject, reasons));
- continue;
}
-
- Logger.Log(LogLevel.INFO,
- reasons.AddInfo(
- $"The user \"{userId}\" is not bucketed into a rollout for feature flag \"{featureFlag.Key}\"."));
- decisions.Add(Result.NewResult(
- new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_ROLLOUT),
- reasons));
}
if (UserProfileService != null && !ignoreUPS && userProfileTracker?.ProfileUpdated == true)
diff --git a/OptimizelySDK/Optimizely.cs b/OptimizelySDK/Optimizely.cs
index 4bc3b568..a84466e4 100644
--- a/OptimizelySDK/Optimizely.cs
+++ b/OptimizelySDK/Optimizely.cs
@@ -855,12 +855,30 @@ private OptimizelyUserContext CreateUserContextCopy(string userId,
);
}
+ public FeatureDecision GetForcedDecision(string flagKey, DecisionReasons decisionReasons,
+ ProjectConfig projectConfig, OptimizelyUserContext user
+ )
+ {
+ var context = new OptimizelyDecisionContext(flagKey);
+ var forcedDecisionVariation =
+ DecisionService.ValidatedForcedDecision(context, projectConfig, user);
+ decisionReasons += forcedDecisionVariation.DecisionReasons;
+ if (forcedDecisionVariation.ResultObject != null)
+ {
+ return new FeatureDecision(null, forcedDecisionVariation.ResultObject,
+ FeatureDecision.DECISION_SOURCE_FEATURE_TEST);
+ }
+
+ return null;
+ }
+
///
/// Returns a decision result ({@link OptimizelyDecision}) for a given flag key and a user context, which contains all data required to deliver the flag.
///
/// - If the SDK finds an error, it’ll return a decision with null for variationKey. The decision will include an error message in reasons.
///
///
+ /// User context to be used to make decision.
/// A flag key for which a decision will be made.
/// A list of options for decision-making.
/// A decision result.
@@ -869,8 +887,6 @@ internal OptimizelyDecision Decide(OptimizelyUserContext user,
OptimizelyDecideOption[] options
)
{
- return DecideForKeys(user, new[] { key }, options)[key];
-
var config = ProjectConfigManager?.GetConfig();
if (config == null)
@@ -879,141 +895,11 @@ OptimizelyDecideOption[] options
ErrorHandler, Logger);
}
- if (key == null)
- {
- return OptimizelyDecision.NewErrorDecision(key,
- user,
- DecisionMessage.Reason(DecisionMessage.FLAG_KEY_INVALID, key),
- ErrorHandler, Logger);
- }
-
- var flag = config.GetFeatureFlagFromKey(key);
- if (flag.Key == null)
- {
- return OptimizelyDecision.NewErrorDecision(key,
- user,
- DecisionMessage.Reason(DecisionMessage.FLAG_KEY_INVALID, key),
- ErrorHandler, Logger);
- }
-
- var userId = user?.GetUserId();
- var userAttributes = user?.GetAttributes();
- var decisionEventDispatched = false;
- var allOptions = GetAllOptions(options);
- var decisionReasons = new DecisionReasons();
- FeatureDecision decision = null;
-
- var decisionContext = new OptimizelyDecisionContext(flag.Key);
- var forcedDecisionVariation =
- DecisionService.ValidatedForcedDecision(decisionContext, config, user);
- decisionReasons += forcedDecisionVariation.DecisionReasons;
-
- if (forcedDecisionVariation.ResultObject != null)
- {
- decision = new FeatureDecision(null, forcedDecisionVariation.ResultObject,
- FeatureDecision.DECISION_SOURCE_FEATURE_TEST);
- }
- else
- {
- var flagDecisionResult = DecisionService.GetVariationForFeature(
- flag,
- user,
- config,
- userAttributes,
- allOptions
- );
- decisionReasons += flagDecisionResult.DecisionReasons;
- decision = flagDecisionResult.ResultObject;
- }
-
- var featureEnabled = false;
-
- if (decision?.Variation != null)
- {
- featureEnabled = decision.Variation.FeatureEnabled.GetValueOrDefault();
- }
+ var filteredOptions = GetAllOptions(options).
+ Where(opt => opt != OptimizelyDecideOption.ENABLED_FLAGS_ONLY).
+ ToArray();
- if (featureEnabled)
- {
- Logger.Log(LogLevel.INFO,
- "Feature \"" + key + "\" is enabled for user \"" + userId + "\"");
- }
- else
- {
- Logger.Log(LogLevel.INFO,
- "Feature \"" + key + "\" is not enabled for user \"" + userId + "\"");
- }
-
- var variableMap = new Dictionary();
- if (flag?.Variables != null &&
- !allOptions.Contains(OptimizelyDecideOption.EXCLUDE_VARIABLES))
- {
- foreach (var featureVariable in flag?.Variables)
- {
- var variableValue = featureVariable.DefaultValue;
- if (featureEnabled)
- {
- var featureVariableUsageInstance =
- decision?.Variation.GetFeatureVariableUsageFromId(featureVariable.Id);
- if (featureVariableUsageInstance != null)
- {
- variableValue = featureVariableUsageInstance.Value;
- }
- }
-
- var typeCastedValue =
- GetTypeCastedVariableValue(variableValue, featureVariable.Type);
-
- if (typeCastedValue is OptimizelyJSON)
- {
- typeCastedValue = ((OptimizelyJSON)typeCastedValue).ToDictionary();
- }
-
- variableMap.Add(featureVariable.Key, typeCastedValue);
- }
- }
-
- var optimizelyJSON = new OptimizelyJSON(variableMap, ErrorHandler, Logger);
-
- var decisionSource = decision?.Source ?? FeatureDecision.DECISION_SOURCE_ROLLOUT;
- if (!allOptions.Contains(OptimizelyDecideOption.DISABLE_DECISION_EVENT))
- {
- decisionEventDispatched = SendImpressionEvent(decision?.Experiment,
- decision?.Variation, userId, userAttributes, config, key, decisionSource,
- featureEnabled);
- }
-
- var reasonsToReport = decisionReasons
- .ToReport(allOptions.Contains(OptimizelyDecideOption.INCLUDE_REASONS))
- .ToArray();
- var variationKey = decision?.Variation?.Key;
-
- // TODO: add ruleKey values when available later. use a copy of experimentKey until then.
- var ruleKey = decision?.Experiment?.Key;
-
- var decisionInfo = new Dictionary
- {
- { "flagKey", key },
- { "enabled", featureEnabled },
- { "variables", variableMap },
- { "variationKey", variationKey },
- { "ruleKey", ruleKey },
- { "reasons", reasonsToReport },
- { "decisionEventDispatched", decisionEventDispatched },
- };
-
- NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision,
- DecisionNotificationTypes.FLAG, userId,
- userAttributes ?? new UserAttributes(), decisionInfo);
-
- return new OptimizelyDecision(
- variationKey,
- featureEnabled,
- optimizelyJSON,
- ruleKey,
- key,
- user,
- reasonsToReport);
+ return DecideForKeys(user, new[] { key }, filteredOptions, true)[key];
}
internal Dictionary DecideAll(OptimizelyUserContext user,
@@ -1038,16 +924,12 @@ OptimizelyDecideOption[] options
internal Dictionary DecideForKeys(OptimizelyUserContext user,
string[] keys,
- OptimizelyDecideOption[] options
+ OptimizelyDecideOption[] options,
+ bool ignoreDefaultOptions = false
)
{
var decisionDictionary = new Dictionary();
- if (keys.Length == 0)
- {
- return decisionDictionary;
- }
-
var projectConfig = ProjectConfigManager?.GetConfig();
if (projectConfig == null)
{
@@ -1056,13 +938,20 @@ OptimizelyDecideOption[] options
return decisionDictionary;
}
- var allOptions = GetAllOptions(options);
+ if (keys.Length == 0)
+ {
+ return decisionDictionary;
+ }
+
+ var allOptions = ignoreDefaultOptions ? options : GetAllOptions(options);
var flagDecisions = new Dictionary();
- var decisionReasons = new Dictionary();
+ var decisionReasonsMap = new Dictionary();
var flagsWithoutForcedDecisions = new List();
+ var validKeys = new List();
+
foreach (var key in keys)
{
var flag = projectConfig.GetFeatureFlagFromKey(key);
@@ -1075,40 +964,175 @@ OptimizelyDecideOption[] options
continue;
}
- var decisionContext = new OptimizelyDecisionContext(flag.Key);
- var forcedDecisionVariation =
- DecisionService.ValidatedForcedDecision(decisionContext, projectConfig, user);
- decisionReasons.Add(key, forcedDecisionVariation.DecisionReasons);
+ validKeys.Add(key);
+
+ var decisionReasons = new DecisionReasons();
+ var forcedDecision = GetForcedDecision(key, decisionReasons, projectConfig, user);
+ decisionReasonsMap.Add(key, decisionReasons);
- if (forcedDecisionVariation.ResultObject != null)
+ if (forcedDecision != null)
{
- var experiment = projectConfig.GetExperimentFromKey(flag.Key);
- var featureDecision = Result.NewResult(
- new FeatureDecision(experiment, forcedDecisionVariation.ResultObject,
- FeatureDecision.DECISION_SOURCE_FEATURE_TEST),
- forcedDecisionVariation.DecisionReasons);
- flagDecisions.Add(key, featureDecision.ResultObject);
+ flagDecisions.Add(key, forcedDecision);
}
else
{
flagsWithoutForcedDecisions.Add(flag);
}
+ }
- var decisionsList = DecisionService.GetVariationsForFeatureList(
- flagsWithoutForcedDecisions, user, projectConfig, user.GetAttributes(),
- options);
+ var decisionsList = DecisionService.GetVariationsForFeatureList(
+ flagsWithoutForcedDecisions, user, projectConfig, user.GetAttributes(),
+ allOptions);
- var decision = Decide(user, key, options);
+ for (var i = 0; i < decisionsList.Count; i += 1)
+ {
+ var decision = decisionsList[i];
+ var flagKey = flagsWithoutForcedDecisions[i].Key;
+ flagDecisions.Add(flagKey, decision.ResultObject);
+ decisionReasonsMap[flagKey] += decision.DecisionReasons;
+ }
+
+ foreach (var key in validKeys)
+ {
+ var flagDecision = flagDecisions[key];
+ var decisionReasons = decisionReasonsMap[key];
+
+ var optimizelyDecision = CreateOptimizelyDecision(user, key, flagDecision,
+ decisionReasons, allOptions.ToList(), projectConfig);
if (!allOptions.Contains(OptimizelyDecideOption.ENABLED_FLAGS_ONLY) ||
- decision.Enabled)
+ optimizelyDecision.Enabled)
{
- decisionDictionary.Add(key, decision);
+ decisionDictionary.Add(key, optimizelyDecision);
}
}
return decisionDictionary;
}
+ private OptimizelyDecision CreateOptimizelyDecision(
+ OptimizelyUserContext user,
+ string flagKey,
+ FeatureDecision flagDecision,
+ DecisionReasons decisionReasons,
+ List allOptions,
+ ProjectConfig projectConfig
+ )
+ {
+ var userId = user.GetUserId();
+
+ var flagEnabled = false;
+ if (flagDecision.Variation != null)
+ {
+ if (flagDecision.Variation.IsFeatureEnabled)
+ {
+ flagEnabled = true;
+ }
+ }
+
+ Logger.Log(LogLevel.INFO,
+ $"Feature \"{flagKey}\" is enabled for user \"{userId}\"? {flagEnabled}");
+
+ var variableMap = new Dictionary();
+ if (!allOptions.Contains(OptimizelyDecideOption.EXCLUDE_VARIABLES))
+ {
+ var decisionVariables = GetDecisionVariableMap(
+ projectConfig.GetFeatureFlagFromKey(flagKey),
+ flagDecision.Variation,
+ flagEnabled);
+ variableMap = decisionVariables.ResultObject;
+ decisionReasons += decisionVariables.DecisionReasons;
+ }
+
+ var optimizelyJson = new OptimizelyJSON(variableMap, ErrorHandler, Logger);
+
+ var decisionSource = FeatureDecision.DECISION_SOURCE_ROLLOUT;
+ if (flagDecision.Source != null)
+ {
+ decisionSource = flagDecision.Source;
+ }
+
+ var reasonsToReport = decisionReasons.ToReport().ToArray();
+ var variationKey = flagDecision.Variation?.Key;
+ // TODO: add ruleKey values when available later. use a copy of experimentKey until then.
+ // add to event metadata as well (currently set to experimentKey)
+ var ruleKey = flagDecision.Experiment?.Key;
+
+ var decisionEventDispatched = false;
+ if (!allOptions.Contains(OptimizelyDecideOption.DISABLE_DECISION_EVENT))
+ {
+ decisionEventDispatched = SendImpressionEvent(
+ flagDecision.Experiment,
+ flagDecision.Variation,
+ userId,
+ user.GetAttributes(),
+ projectConfig,
+ flagKey,
+ decisionSource,
+ flagEnabled);
+ }
+
+ var decisionInfo = new Dictionary
+ {
+ { "featureKey", flagKey },
+ { "featureEnabled", flagEnabled },
+ { "variableValues", variableMap },
+ { "variationKey", variationKey },
+ { "ruleKey", ruleKey },
+ { "reasons", reasonsToReport },
+ { "decisionEventDispatched", decisionEventDispatched },
+ };
+
+ // var decisionNotificationType =
+ // projectConfig.IsFeatureExperiment(flagDecision.Experiment?.Id) ?
+ // DecisionNotificationTypes.FEATURE_TEST :
+ // DecisionNotificationTypes.AB_TEST;
+ NotificationCenter.SendNotifications(NotificationCenter.NotificationType.Decision,
+ userId,
+ user.GetAttributes(), decisionInfo);
+
+ return new OptimizelyDecision(
+ variationKey,
+ flagEnabled,
+ optimizelyJson,
+ ruleKey,
+ flagKey,
+ user,
+ reasonsToReport);
+ }
+
+ private Result> GetDecisionVariableMap(FeatureFlag flag, Variation variation, bool featureEnabled)
+ {
+ var reasons = new DecisionReasons();
+ var valuesMap = new Dictionary();
+
+ foreach (var variable in flag.Variables)
+ {
+ var value = variable.DefaultValue;
+ if (featureEnabled)
+ {
+ var instance = variation.GetFeatureVariableUsageFromId(variable.Id);
+ if (instance != null)
+ {
+ value = instance.Value;
+ }
+ }
+
+ var convertedValue = GetTypeCastedVariableValue(value, variable.Type);
+ if (convertedValue == null)
+ {
+ reasons.AddError(DecisionMessage.Reason(DecisionMessage.VARIABLE_VALUE_INVALID, variable.Key));
+ }
+ else if (convertedValue is OptimizelyJSON optimizelyJson)
+ {
+ convertedValue = optimizelyJson.ToDictionary();
+ }
+
+ valuesMap[variable.Key] = convertedValue;
+ }
+
+ return Result>.NewResult(valuesMap, reasons);
+ }
+
private OptimizelyDecideOption[] GetAllOptions(OptimizelyDecideOption[] options)
{
var copiedOptions = DefaultDecideOptions;