Skip to content
Open
52 changes: 28 additions & 24 deletions Sources/Implementation/DefaultDecisionService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -658,18 +658,20 @@ class DefaultDecisionService: OPTDecisionService {
}

// check local holdouts targeting this rule
let localHoldouts = config.getHoldoutsForRule(ruleId: rule.id)
for holdout in localHoldouts {
let holdoutDecision = getVariationForHoldout(config: config,
flagKey: flagKey,
holdout: holdout,
user: user,
options: options)
reasons.merge(holdoutDecision.reasons)
if let variation = holdoutDecision.result {
// User is in holdout — return holdout variation immediately, skip this rule
let variationDecision = VariationDecision(variation: variation, holdout: holdout)
return DecisionResponse(result: variationDecision, reasons: reasons)
if FeatureGates.localHoldouts {
let localHoldouts = config.getHoldoutsForRule(ruleId: rule.id)
for holdout in localHoldouts {
let holdoutDecision = getVariationForHoldout(config: config,
flagKey: flagKey,
holdout: holdout,
user: user,
options: options)
reasons.merge(holdoutDecision.reasons)
if let variation = holdoutDecision.result {
// User is in holdout — return holdout variation immediately, skip this rule
let variationDecision = VariationDecision(variation: variation, holdout: holdout)
return DecisionResponse(result: variationDecision, reasons: reasons)
}
}
}

Expand Down Expand Up @@ -716,18 +718,20 @@ class DefaultDecisionService: OPTDecisionService {
}

// check local holdouts targeting this delivery rule
let localHoldouts = config.getHoldoutsForRule(ruleId: rule.id)
for holdout in localHoldouts {
let holdoutDecision = getVariationForHoldout(config: config,
flagKey: flagKey,
holdout: holdout,
user: user,
options: options)
reasons.merge(holdoutDecision.reasons)
if let variation = holdoutDecision.result {
// User is in holdout — return holdout variation with holdout info
let decision = DeliveryRuleDecision(variation: variation, skipToEveryoneElse: skipToEveryoneElse, holdout: holdout)
return DecisionResponse(result: decision, reasons: reasons)
if FeatureGates.localHoldouts {
let localHoldouts = config.getHoldoutsForRule(ruleId: rule.id)
for holdout in localHoldouts {
let holdoutDecision = getVariationForHoldout(config: config,
flagKey: flagKey,
holdout: holdout,
user: user,
options: options)
reasons.merge(holdoutDecision.reasons)
if let variation = holdoutDecision.result {
// User is in holdout — return holdout variation with holdout info
let decision = DeliveryRuleDecision(variation: variation, skipToEveryoneElse: skipToEveryoneElse, holdout: holdout)
return DecisionResponse(result: decision, reasons: reasons)
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/Utils/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

import Foundation

struct FeatureGates {
static var localHoldouts = false
}

struct Constants {
struct Attributes {
static let reservedBucketIdAttribute = "$opt_bucketing_id"
Expand Down
12 changes: 9 additions & 3 deletions Tests/OptimizelyTests-Common/DecisionListenerTest_Holdouts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,14 @@ class DecisionListenerTests_Holdouts: XCTestCase {

override func setUp() {
super.setUp()

FeatureGates.localHoldouts = true

optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey,
eventDispatcher: eventDispatcher,
userProfileService: OTUtils.createClearUserProfileService())

try! optimizely.start(datafile: OTUtils.loadJSONDatafile("decide_datafile")!)

var holdout = try! OTUtils.model(from: sampleHoldout) as Holdout
// Audience "13389130056" requires "country" = "US"
holdout.audienceIds = ["13389130056"]
Expand All @@ -79,6 +80,11 @@ class DecisionListenerTests_Holdouts: XCTestCase {

self.notificationCenter = self.optimizely.notificationCenter!
}

override func tearDown() {
FeatureGates.localHoldouts = false
super.tearDown()
}

func testDecisionListenerDecideWithUserInHoldout() {
let exp = expectation(description: "x")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class DecisionServiceTests_Holdouts: XCTestCase {

override func setUp() {
super.setUp()

FeatureGates.localHoldouts = true
self.optimizely = OTUtils.createOptimizely(datafileName: "empty_datafile",
clearUserProfileService: true)
self.config = self.optimizely.config!
Expand All @@ -212,6 +212,11 @@ class DecisionServiceTests_Holdouts: XCTestCase {
self.config.holdoutConfig.allHoldouts = [holdout]
}

override func tearDown() {
FeatureGates.localHoldouts = false
super.tearDown()
}

}

// MARK: - Test doesMeetAudienceConditions()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class DecisionServiceTests_LocalHoldouts: XCTestCase {

override func setUp() {
super.setUp()
FeatureGates.localHoldouts = true

// Load a real datafile for testing
optimizely = OTUtils.createOptimizely(datafileName: "decide_datafile",
Expand All @@ -60,6 +61,11 @@ class DecisionServiceTests_LocalHoldouts: XCTestCase {
decisionService = optimizely.decisionService as? DefaultDecisionService
}

override func tearDown() {
FeatureGates.localHoldouts = false
super.tearDown()
}

// MARK: - Global Holdouts Tests

func testGlobalHoldout_EvaluatedBeforeAllRules() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,19 @@ class OptimizelyUserContextTests_Decide_Holdouts: XCTestCase {

override func setUp() {
super.setUp()

FeatureGates.localHoldouts = true

optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey,
eventDispatcher: eventDispatcher,
userProfileService: OTUtils.createClearUserProfileService())

try! optimizely.start(datafile: OTUtils.loadJSONDatafile("decide_datafile")!)
}

override func tearDown() {
FeatureGates.localHoldouts = false
super.tearDown()
}

func test_decide_with_global_holdout_audience_matched() {
let featureKey = "feature_1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,18 @@ class OptimizelyUserContextTests_Decide_With_Holdouts_Reasons: XCTestCase {

override func setUp() {
super.setUp()

FeatureGates.localHoldouts = true

optimizely = OptimizelyClient(sdkKey: OTUtils.randomSdkKey,
userProfileService: OTUtils.createClearUserProfileService())

try! optimizely.start(datafile: OTUtils.loadJSONDatafile("decide_datafile")!)
}

override func tearDown() {
FeatureGates.localHoldouts = false
super.tearDown()
}

/// Test when user is bucketed into the global holdout
func testDecideReasons_userBucketedIntoGlobalHoldout() {
Expand Down
1 change: 1 addition & 0 deletions Tests/OptimizelyTests-DataModel/HoldoutConfigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import XCTest

class HoldoutConfigTests: XCTestCase {

func testEmptyHoldouts_shouldHaveEmptyMaps() {
let config = HoldoutConfig(allholdouts: [])

Expand Down
Loading