Skip to content

Commit 99ccb38

Browse files
rollout bucketing changed (#223)
* rollout bucketing changed * a test for fallback rule with bucketing id
1 parent a4424f7 commit 99ccb38

File tree

4 files changed

+63
-44
lines changed

4 files changed

+63
-44
lines changed

OptimizelySDKCore/OptimizelySDKCore/OPTLYDecisionService.m

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -287,37 +287,33 @@ - (OPTLYFeatureDecision *)getVariationForFeatureRollout:(OPTLYFeatureFlag *)feat
287287
// Evaluate this user for the next rule
288288
continue;
289289
}
290-
[self.config.logger logMessage:[NSString stringWithFormat:OPTLYLoggerMessagesDecisionServiceFRBucketing, userId, experiment.experimentKey]
291-
withLevel:OptimizelyLogLevelDebug];
292290

293291
// Evaluate if user satisfies the traffic allocation for this rollout rule
294292
OPTLYVariation *variation = [self.bucketer bucketExperiment:experiment withBucketingId:bucketing_id];
295-
if (variation && variation.variationKey) {
296-
[self.config.logger logMessage:[NSString stringWithFormat:OPTLYLoggerMessagesDecisionServiceFRUserBucketed, userId, featureFlagKey]
297-
withLevel:OptimizelyLogLevelDebug];
298-
OPTLYFeatureDecision *decision = [[OPTLYFeatureDecision alloc] initWithExperiment:experiment
299-
variation:variation
300-
source:DecisionSourceRollout];
301-
return decision;
302-
} else {
303-
[self.config.logger logMessage:[NSString stringWithFormat:OPTLYLoggerMessagesDecisionServiceFRUserNotBucketed, userId, featureFlagKey]
304-
withLevel:OptimizelyLogLevelDebug];
293+
if (!variation || !variation.variationKey) {
305294
break;
306295
}
307-
}
308-
// Evaluate Everyone Else Rule / Last Rule now
309-
OPTLYExperiment *experiment = rolloutRules[rolloutRules.count - 1];
310-
OPTLYVariation *variation = [self.bucketer bucketExperiment:experiment withBucketingId:bucketing_id];
311-
if (variation && variation.variationKey) {
296+
297+
[self.config.logger logMessage:[NSString stringWithFormat:OPTLYLoggerMessagesDecisionServiceFRUserBucketed, userId, featureFlagKey]
298+
withLevel:OptimizelyLogLevelDebug];
312299
OPTLYFeatureDecision *decision = [[OPTLYFeatureDecision alloc] initWithExperiment:experiment
313300
variation:variation
314301
source:DecisionSourceRollout];
315302
return decision;
316-
} else {
317-
[self.config.logger logMessage:[NSString stringWithFormat:OPTLYLoggerMessagesDecisionServiceFRUserExcludedEveryoneElse, userId]
318-
withLevel:OptimizelyLogLevelDebug];
319-
return nil;
320303
}
304+
// Evaluate fall back rule / last rule now
305+
OPTLYExperiment *experiment = rolloutRules[rolloutRules.count - 1];
306+
if ([self userPassesTargeting:self.config experiment:experiment userId:userId attributes:attributes]) {
307+
OPTLYVariation *variation = [self.bucketer bucketExperiment:experiment withBucketingId:bucketing_id];
308+
if (variation && variation.variationKey) {
309+
OPTLYFeatureDecision *decision = [[OPTLYFeatureDecision alloc] initWithExperiment:experiment
310+
variation:variation
311+
source:DecisionSourceRollout];
312+
return decision;
313+
}
314+
}
315+
316+
return nil;
321317
}
322318

323319
- (void)saveUserProfile:(NSDictionary *)userProfileDict

OptimizelySDKCore/OptimizelySDKCore/OPTLYLoggerMessages.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,8 @@ extern NSString *const OPTLYLoggerMessagesDecisionServiceFFUserBucketed;
221221
extern NSString *const OPTLYLoggerMessagesDecisionServiceFFUserNotBucketed;
222222
// FF = Feature Rollout
223223
extern NSString *const OPTLYLoggerMessagesDecisionServiceFRNotUsed;
224-
extern NSString *const OPTLYLoggerMessagesDecisionServiceFRBucketing;
225224
extern NSString *const OPTLYLoggerMessagesDecisionServiceFRUserBucketed;
226225
extern NSString *const OPTLYLoggerMessagesDecisionServiceFRUserExcluded;
227-
extern NSString *const OPTLYLoggerMessagesDecisionServiceFRUserExcludedEveryoneElse;
228226
extern NSString *const OPTLYLoggerMessagesDecisionServiceFRUserNotBucketed;
229227
extern NSString *const OPTLYLoggerMessagesDecisionServiceUserBucketed;
230228
extern NSString *const OPTLYLoggerMessagesDecisionServiceUserNotBucketed;

OptimizelySDKCore/OptimizelySDKCore/OPTLYLoggerMessages.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,8 @@
215215
NSString *const OPTLYLoggerMessagesDecisionServiceFFUserBucketed = @"[DECISION SERVICE] User %@ is bucketed into experiment %@ of feature %@.";
216216
NSString *const OPTLYLoggerMessagesDecisionServiceFFUserNotBucketed = @"[DECISION SERVICE] User %@ is not bucketed into any of the experiments using the feature %@.";
217217
NSString *const OPTLYLoggerMessagesDecisionServiceFRNotUsed = @"[DECISION SERVICE] Feature flag %@ is not used in a rollout.";
218-
NSString *const OPTLYLoggerMessagesDecisionServiceFRBucketing = @"[DECISION SERVICE] Attempting to bucket user %@ into rollout rule %@.";
219218
NSString *const OPTLYLoggerMessagesDecisionServiceFRUserBucketed = @"[DECISION SERVICE] User %@ is bucketed into rollout for feature flag %@.";
220219
NSString *const OPTLYLoggerMessagesDecisionServiceFRUserExcluded = @"[DECISION SERVICE] User %@ was excluded due to traffic allocation. Checking 'Everyone Else' rule now.";
221-
NSString *const OPTLYLoggerMessagesDecisionServiceFRUserExcludedEveryoneElse = @"[DECISION SERVICE] User %@ was excluded from the 'Everyone Else' rule for feature flag.";
222220
NSString *const OPTLYLoggerMessagesDecisionServiceFRUserNotBucketed = @"[DECISION SERVICE] User %@ is not bucketed into rollout for feature flag %@.";
223221
NSString *const OPTLYLoggerMessagesDecisionServiceUserBucketed = @"[DECISION SERVICE] User with bucketing ID %@ is in experiment %@ of group %@.";
224222
NSString *const OPTLYLoggerMessagesDecisionServiceUserNotBucketed = @"[DECISION SERVICE] User with bucketing ID %@ is not in any experiments of group %@.";

OptimizelySDKCore/OptimizelySDKCoreTests/OPTLYDecisionServiceTest.m

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -595,13 +595,13 @@ - (void)testGetVariationForFeatureWithNoRule {
595595
XCTAssertNil(decision, @"Get variation for feature with rollout having no rule should return nil: %@", decision);
596596
}
597597

598-
// should return nil when the user is not bucketed into targeting rule as well as "Everyone Else" rule.
598+
// should return nil when the user is not bucketed into targeting rule as well as "Fall Back" rule.
599599
- (void)testGetVariationForFeatureWithNoBucketing {
600600
OPTLYFeatureFlag *booleanFeatureFlag = [self.config getFeatureFlagForKey:kFeatureFlagNoBucketedRuleRolloutKey];
601601
NSString *rolloutId = booleanFeatureFlag.rolloutId;
602602
OPTLYRollout *rollout = [self.config getRolloutForId:rolloutId];
603603
OPTLYExperiment *experiment = rollout.experiments[0];
604-
OPTLYExperiment *everyoneElseRule = rollout.experiments[rollout.experiments.count - 1];
604+
OPTLYExperiment *fallBackRule = rollout.experiments[rollout.experiments.count - 1];
605605
NSDictionary *userAttributes = @{ kAttributeKey: kAttributeValueChrome };
606606

607607
id bucketerMock = OCMPartialMock(self.bucketer);
@@ -613,7 +613,7 @@ - (void)testGetVariationForFeatureWithNoBucketing {
613613
XCTAssertNil(decision, @"Get variation for feature with rollout having no bucketing rule should return nil: %@", decision);
614614

615615
OCMVerify([bucketerMock bucketExperiment:experiment withBucketingId:kUserId]);
616-
OCMVerify([bucketerMock bucketExperiment:everyoneElseRule withBucketingId:kUserId]);
616+
OCMVerify([bucketerMock bucketExperiment:fallBackRule withBucketingId:kUserId]);
617617
[bucketerMock stopMocking];
618618
}
619619

@@ -643,57 +643,84 @@ - (void)testGetVariationForFeatureWithTargetingRuleBucketing {
643643
[bucketerMock stopMocking];
644644
}
645645

646-
// should return variation when the user is bucketed into "Everyone Else" rule instead of targeting rule
647-
- (void)testGetVariationForFeatureWithEveryoneElseRuleBucketing {
646+
// should return variation when the user is bucketed into "Fall Back" rule instead of targeting rule
647+
- (void)testGetVariationForFeatureWithFallBackRuleBucketing {
648648
OPTLYFeatureFlag *booleanFeatureFlag = [self.config getFeatureFlagForKey:kFeatureFlagNoBucketedRuleRolloutKey];
649649
NSString *rolloutId = booleanFeatureFlag.rolloutId;
650650
OPTLYRollout *rollout = [self.config getRolloutForId:rolloutId];
651651
OPTLYExperiment *experiment = rollout.experiments[0];
652-
OPTLYExperiment *everyoneElseRule = rollout.experiments[rollout.experiments.count - 1];
653-
OPTLYVariation *expectedVariation = everyoneElseRule.variations[0];
654-
OPTLYFeatureDecision *expectedDecision = [[OPTLYFeatureDecision alloc] initWithExperiment:everyoneElseRule
652+
OPTLYExperiment *fallBackRule = rollout.experiments[rollout.experiments.count - 1];
653+
OPTLYVariation *expectedVariation = fallBackRule.variations[0];
654+
OPTLYFeatureDecision *expectedDecision = [[OPTLYFeatureDecision alloc] initWithExperiment:fallBackRule
655655
variation:expectedVariation
656656
source:DecisionSourceRollout];
657657
NSDictionary *userAttributes = @{ kAttributeKey: kAttributeValueChrome };
658658

659659
id bucketerMock = OCMPartialMock(self.bucketer);
660660
OCMStub([bucketerMock bucketExperiment:experiment withBucketingId:[OCMArg any]]).andReturn(nil);
661-
OCMStub([bucketerMock bucketExperiment:everyoneElseRule withBucketingId:[OCMArg any]]).andReturn(expectedVariation);
661+
OCMStub([bucketerMock bucketExperiment:fallBackRule withBucketingId:kAttributeValueChrome]).andReturn(expectedVariation);
662662
OPTLYDecisionService *decisionService = [[OPTLYDecisionService alloc] initWithProjectConfig:self.config bucketer:bucketerMock];
663663

664664
OPTLYFeatureDecision *decision = [decisionService getVariationForFeature:booleanFeatureFlag userId:kUserId attributes:userAttributes];
665665

666-
XCTAssertNotNil(decision, @"Get variation for feature with rollout having everyone else rule should return variation: %@", decision);
666+
XCTAssertNotNil(decision, @"Get variation for feature with rollout having fall back rule should return variation: %@", decision);
667667
XCTAssertEqualObjects(decision.variation, expectedDecision.variation);
668668

669669
OCMVerify([bucketerMock bucketExperiment:experiment withBucketingId:kUserId]);
670-
OCMVerify([bucketerMock bucketExperiment:everyoneElseRule withBucketingId:kUserId]);
670+
OCMVerify([bucketerMock bucketExperiment:fallBackRule withBucketingId:kUserId]);
671671
[bucketerMock stopMocking];
672672
}
673673

674-
// should return variation when the user is bucketed into "Everyone Else" after attempting to bucket into all targeting rules
675-
- (void)testGetVariationForFeatureWithEveryoneElseRuleBucketingButNoTargetingRule {
674+
// should return variation when the user is bucketed into "Fall Back" after attempting to bucket into all targeting rules
675+
- (void)testGetVariationForFeatureWithFallBackRuleBucketingButNoTargetingRule {
676676
OPTLYFeatureFlag *booleanFeatureFlag = [self.config getFeatureFlagForKey:kFeatureFlagNoBucketedRuleRolloutKey];
677677
NSString *rolloutId = booleanFeatureFlag.rolloutId;
678678
OPTLYRollout *rollout = [self.config getRolloutForId:rolloutId];
679-
OPTLYExperiment *everyoneElseRule = rollout.experiments[rollout.experiments.count - 1];
680-
OPTLYVariation *expectedVariation = everyoneElseRule.variations[0];
681-
OPTLYFeatureDecision *expectedDecision = [[OPTLYFeatureDecision alloc] initWithExperiment:everyoneElseRule
679+
OPTLYExperiment *fallBackRule = rollout.experiments[rollout.experiments.count - 1];
680+
OPTLYVariation *expectedVariation = fallBackRule.variations[0];
681+
OPTLYFeatureDecision *expectedDecision = [[OPTLYFeatureDecision alloc] initWithExperiment:fallBackRule
682682
variation:expectedVariation
683683
source:DecisionSourceRollout];
684684

685685
id bucketerMock = OCMPartialMock(self.bucketer);
686-
OCMStub([bucketerMock bucketExperiment:everyoneElseRule withBucketingId:[OCMArg any]]).andReturn(expectedVariation);
686+
OCMStub([bucketerMock bucketExperiment:fallBackRule withBucketingId:[OCMArg any]]).andReturn(expectedVariation);
687687
OPTLYDecisionService *decisionService = [[OPTLYDecisionService alloc] initWithProjectConfig:self.config bucketer:bucketerMock];
688688

689689
// Provide null attributes so that user does not qualify for audience.
690690
OPTLYFeatureDecision *decision = [decisionService getVariationForFeature:booleanFeatureFlag userId:kUserId attributes:nil];
691691

692-
XCTAssertNotNil(decision, @"Get variation for feature with rollout having everyone else rule after failing all targeting rules should return variation: %@", decision);
692+
XCTAssertNotNil(decision, @"Get variation for feature with rollout having fall back rule after failing all targeting rules should return variation: %@", decision);
693693
XCTAssertEqualObjects(decision.variation, expectedDecision.variation);
694694

695-
OCMVerify([bucketerMock bucketExperiment:everyoneElseRule withBucketingId:kUserId]);
695+
OCMVerify([bucketerMock bucketExperiment:fallBackRule withBucketingId:kUserId]);
696696
[bucketerMock stopMocking];
697697
}
698698

699+
- (void)testGetVariationForFeatureWithFallBackRuleBucketingId {
700+
OPTLYFeatureFlag *featureFlag = [self.config getFeatureFlagForKey:kFeatureFlagNoBucketedRuleRolloutKey];
701+
OPTLYRollout *rollout = [self.config getRolloutForId:featureFlag.rolloutId];
702+
OPTLYExperiment *rolloutRuleExperiment = rollout.experiments[rollout.experiments.count - 1];
703+
OPTLYVariation *rolloutVariation = rolloutRuleExperiment.variations[0];
704+
NSString *bucketingId = @"user_bucketing_id";
705+
NSString *userId = @"user_id";
706+
NSDictionary *attributes = @{OptimizelyBucketId: bucketingId};
707+
708+
id bucketerMock = OCMPartialMock(self.bucketer);
709+
OCMStub([bucketerMock bucketExperiment:rolloutRuleExperiment withBucketingId:userId]).andReturn(nil);
710+
OCMStub([bucketerMock bucketExperiment:rolloutRuleExperiment withBucketingId:bucketingId]).andReturn(rolloutVariation);
711+
712+
OPTLYDecisionService *decisionService = [[OPTLYDecisionService alloc] initWithProjectConfig:self.config
713+
bucketer:bucketerMock];
714+
715+
OPTLYFeatureDecision *expectedFeatureDecision = [[OPTLYFeatureDecision alloc] initWithExperiment:rolloutRuleExperiment
716+
variation:rolloutVariation
717+
source:DecisionSourceRollout];
718+
719+
OPTLYFeatureDecision *featureDecision = [decisionService getVariationForFeature:featureFlag userId:userId attributes:attributes];
720+
721+
XCTAssertEqualObjects(expectedFeatureDecision.experiment, featureDecision.experiment);
722+
XCTAssertEqualObjects(expectedFeatureDecision.variation, featureDecision.variation);
723+
XCTAssertEqualObjects(expectedFeatureDecision.source, featureDecision.source);
724+
}
725+
699726
@end

0 commit comments

Comments
 (0)