Skip to content

Commit 809d81f

Browse files
[FSSDK-11546] decision service adjustmen
1 parent 150380f commit 809d81f

File tree

4 files changed

+95
-9
lines changed

4 files changed

+95
-9
lines changed

OptimizelySDK/Bucketing/Bucketer.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ IEnumerable<TrafficAllocation> trafficAllocations
112112
/// <param name="bucketingId">A customer-assigned value used to create the key for the murmur hash.</param>
113113
/// <param name="userId">User identifier</param>
114114
/// <returns>Variation which will be shown to the user</returns>
115-
public virtual Result<Variation> Bucket(ProjectConfig config, Experiment experiment,
115+
public virtual Result<Variation> Bucket(ProjectConfig config, ExperimentCore experiment,
116116
string bucketingId, string userId
117117
)
118118
{
@@ -127,9 +127,9 @@ public virtual Result<Variation> Bucket(ProjectConfig config, Experiment experim
127127
}
128128

129129
// Determine if experiment is in a mutually exclusive group.
130-
if (experiment.IsInMutexGroup)
130+
if (experiment is Experiment exp && exp.IsInMutexGroup)
131131
{
132-
var group = config.GetGroup(experiment.GroupId);
132+
var group = config.GetGroup(exp.GroupId);
133133
if (string.IsNullOrEmpty(group.Id))
134134
{
135135
return Result<Variation>.NewResult(new Variation(), reasons);
@@ -147,13 +147,13 @@ public virtual Result<Variation> Bucket(ProjectConfig config, Experiment experim
147147
if (userExperimentId != experiment.Id)
148148
{
149149
message =
150-
$"User [{userId}] is not in experiment [{experiment.Key}] of group [{experiment.GroupId}].";
150+
$"User [{userId}] is not in experiment [{exp.Key}] of group [{exp.GroupId}].";
151151
Logger.Log(LogLevel.INFO, reasons.AddInfo(message));
152152
return Result<Variation>.NewResult(new Variation(), reasons);
153153
}
154154

155155
message =
156-
$"User [{userId}] is in experiment [{experiment.Key}] of group [{experiment.GroupId}].";
156+
$"User [{userId}] is in experiment [{exp.Key}] of group [{exp.GroupId}].";
157157
Logger.Log(LogLevel.INFO, reasons.AddInfo(message));
158158
}
159159

OptimizelySDK/Bucketing/DecisionService.cs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using OptimizelySDK.Logger;
2323
using OptimizelySDK.OptimizelyDecisions;
2424
using OptimizelySDK.Utils;
25+
using static OptimizelySDK.Entity.Holdout;
2526

2627
namespace OptimizelySDK.Bucketing
2728
{
@@ -754,7 +755,22 @@ OptimizelyDecideOption[] options
754755
{
755756
var reasons = new DecisionReasons();
756757
reasons += upsReasons;
758+
var holdouts = projectConfig.GetHoldoutsForFlag(featureFlag.Key);
759+
foreach (var holdout in holdouts)
760+
{
761+
var holdoutDecision = GetVariationForHoldout(holdout, user, projectConfig);
762+
reasons += holdoutDecision.DecisionReasons;
757763

764+
if (holdoutDecision.ResultObject != null)
765+
{
766+
Logger.Log(LogLevel.INFO,
767+
reasons.AddInfo(
768+
$"The user \"{userId}\" is bucketed into holdout \"{holdout.Key}\" for feature flag \"{featureFlag.Key}\"."));
769+
decisions.Add(Result<FeatureDecision>.NewResult(holdoutDecision.ResultObject,
770+
reasons));
771+
continue;
772+
}
773+
}
758774
// Check if the feature flag has an experiment and the user is bucketed into that experiment.
759775
var decisionResult = GetVariationForFeatureExperiment(featureFlag, user,
760776
filteredAttributes, projectConfig, options, userProfileTracker);
@@ -856,6 +872,76 @@ private Result<string> GetBucketingId(string userId, UserAttributes filteredAttr
856872
return Result<string>.NewResult(bucketingId, reasons);
857873
}
858874

875+
private Result<FeatureDecision> GetVariationForHoldout(
876+
Holdout holdout,
877+
OptimizelyUserContext user,
878+
ProjectConfig config
879+
)
880+
{
881+
var userId = user.GetUserId();
882+
var reasons = new DecisionReasons();
883+
884+
if (!holdout.IsActivated)
885+
{
886+
reasons.AddInfo("Holdout ({0}) is not running.", holdout.Key);
887+
return Result<FeatureDecision>.NewResult(
888+
new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_HOLDOUT),
889+
reasons
890+
);
891+
}
892+
893+
var audienceResult = ExperimentUtils.DoesUserMeetAudienceConditions(
894+
config,
895+
holdout,
896+
user,
897+
LOGGING_KEY_TYPE_EXPERIMENT,
898+
holdout.Key,
899+
Logger
900+
);
901+
reasons += audienceResult.DecisionReasons;
902+
903+
if (!audienceResult.ResultObject)
904+
{
905+
reasons.AddInfo(
906+
"User ({0}) does not meet conditions for holdout ({1}).",
907+
userId,
908+
holdout.Key
909+
);
910+
return Result<FeatureDecision>.NewResult(
911+
new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_HOLDOUT),
912+
reasons
913+
);
914+
}
915+
916+
var attributes = user.GetAttributes();
917+
var bucketingIdResult = GetBucketingId(userId, attributes);
918+
var bucketedVariation = Bucketer.Bucket(config, holdout, bucketingIdResult.ResultObject, userId);
919+
reasons += bucketedVariation.DecisionReasons;
920+
921+
if (bucketedVariation.ResultObject != null)
922+
{
923+
reasons.AddInfo(
924+
"User ({0}) is bucketed into holdout variation ({1}).",
925+
userId,
926+
bucketedVariation.ResultObject.Key
927+
);
928+
return Result<FeatureDecision>.NewResult(
929+
new FeatureDecision(holdout, bucketedVariation.ResultObject, FeatureDecision.DECISION_SOURCE_HOLDOUT),
930+
reasons
931+
);
932+
}
933+
934+
reasons.AddInfo(
935+
"User ({0}) is not bucketed into holdout variation ({1}).",
936+
userId,
937+
holdout.Key
938+
);
939+
940+
return Result<FeatureDecision>.NewResult(
941+
new FeatureDecision(null, null, FeatureDecision.DECISION_SOURCE_HOLDOUT),
942+
reasons
943+
);
944+
}
859945
/// <summary>
860946
/// Finds a validated forced decision.
861947
/// </summary>

OptimizelySDK/Entity/FeatureDecision.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ public class FeatureDecision
2020
{
2121
public const string DECISION_SOURCE_FEATURE_TEST = "feature-test";
2222
public const string DECISION_SOURCE_ROLLOUT = "rollout";
23-
24-
public Experiment Experiment { get; }
23+
public const string DECISION_SOURCE_HOLDOUT = "holdout";
24+
public ExperimentCore Experiment { get; }
2525
public Variation Variation { get; }
2626
public string Source { get; }
2727

28-
public FeatureDecision(Experiment experiment, Variation variation, string source)
28+
public FeatureDecision(ExperimentCore experiment, Variation variation, string source)
2929
{
3030
Experiment = experiment;
3131
Variation = variation;

OptimizelySDK/Utils/ExperimentUtils.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static bool IsExperimentActive(Experiment experiment, ILogger logger)
4646
/// <param name="logger">Custom logger implementation to record log outputs</param>
4747
/// <returns>true if the user meets audience conditions to be in experiment, false otherwise.</returns>
4848
public static Result<bool> DoesUserMeetAudienceConditions(ProjectConfig config,
49-
Experiment experiment,
49+
ExperimentCore experiment,
5050
OptimizelyUserContext user,
5151
string loggingKeyType,
5252
string loggingKey,

0 commit comments

Comments
 (0)