Skip to content

Commit f29107f

Browse files
(feat): OnDecision Notification Listener for isFeatureEnabled/getEnabledFeatures API (#128)
* Initial implementations. * DemoSwiftApp updated. * Test-cases updated. * Implementation updated and testcases added. * Recommended changes made. * Minor bug fixed. * Recommended changes made.
1 parent 167f579 commit f29107f

File tree

4 files changed

+180
-9
lines changed

4 files changed

+180
-9
lines changed

OptimizelySDK/Optimizely/OptimizelyManager.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,27 @@ open class OptimizelyManager: NSObject {
410410
// fix DecisionService to throw error
411411
let pair = decisionService.getVariationForFeature(config: config, featureFlag: featureFlag, userId: userId, attributes: attributes ?? OptimizelyAttributes())
412412

413+
var args: Array<Any?> = (self.notificationCenter as! DefaultNotificationCenter).getArgumentsForDecisionListener(notificationType: Constants.DecisionTypeKeys.isFeatureEnabled, userId: userId, attributes: attributes)
414+
415+
var decisionInfo = Dictionary<String,Any>()
416+
decisionInfo[Constants.DecisionInfoKeys.feature] = featureKey
417+
decisionInfo[Constants.DecisionInfoKeys.source] = Constants.DecisionSource.Rollout
418+
decisionInfo[Constants.DecisionInfoKeys.featureEnabled] = false
419+
413420
guard let variation = pair?.variation else {
421+
args.append(decisionInfo)
422+
self.notificationCenter.sendNotifications(type: NotificationType.Decision.rawValue, args: args)
414423
throw OptimizelyError.variationUnknown
415424
}
416425

417426
let featureEnabled = variation.featureEnabled ?? false
418427

419428
// we came from an experiment if experiment is not nil
420429
if let experiment = pair?.experiment {
430+
431+
decisionInfo[Constants.DecisionInfoKeys.sourceExperiment] = experiment.key
432+
decisionInfo[Constants.DecisionInfoKeys.sourceVariation] = variation.key
433+
421434
// TODO: fix to throw errors
422435
guard let body = BatchEventBuilder.createImpressionEvent(config: config,
423436
decisionService: decisionService,
@@ -445,6 +458,11 @@ open class OptimizelyManager: NSObject {
445458
}
446459
self.notificationCenter.sendNotifications(type: NotificationType.Activate.rawValue, args: [experiment, userId, attributes, variation, ["url":event.url as Any, "body":event.body as Any]])
447460
}
461+
462+
decisionInfo[Constants.DecisionInfoKeys.featureEnabled] = featureEnabled
463+
decisionInfo[Constants.DecisionInfoKeys.source] = (pair?.experiment != nil ? Constants.DecisionSource.Experiment : Constants.DecisionSource.Rollout)
464+
args.append(decisionInfo)
465+
self.notificationCenter.sendNotifications(type: NotificationType.Decision.rawValue, args: args)
448466

449467
return featureEnabled
450468
}

OptimizelySDK/OptimizelyTests/OptimizelyTests-Common/DecisionListenerTests.swift

Lines changed: 160 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,113 @@
1919
import XCTest
2020

2121
class DecisionListenerTests: XCTestCase {
22-
22+
23+
// MARK: - Constants
24+
25+
let kFeatureKey = "feature_1"
26+
let kUserId = "11111"
27+
28+
// MARK: - Properties
29+
30+
var datafile: Data!
31+
var optimizely: FakeManager!
32+
33+
// MARK: - SetUp
34+
35+
override func setUp() {
36+
super.setUp()
37+
38+
self.datafile = OTUtils.loadJSONDatafile("typed_audience_datafile")
39+
40+
self.optimizely = FakeManager(sdkKey: "12345",
41+
userProfileService: OTUtils.createClearUserProfileService())
42+
try! self.optimizely.initializeSDK(datafile: datafile)
43+
}
44+
45+
func testDecisionListenerForGetEnabledFeatures() {
46+
47+
let tmpDatafile = OTUtils.loadJSONDatafile("api_datafile")
48+
49+
let tmpOptimizely = OptimizelyManager(sdkKey: "12345",
50+
userProfileService: OTUtils.createClearUserProfileService())
51+
try! tmpOptimizely.initializeSDK(datafile: tmpDatafile!)
52+
53+
var count = 0
54+
tmpOptimizely.notificationCenter.clearAllNotificationListeners()
55+
_ = tmpOptimizely.notificationCenter.addDecisionNotificationListener { (type, userId, attributes, decisionInfo) in
56+
count += 1
57+
}
58+
59+
_ = try! tmpOptimizely.getEnabledFeatures(userId: kUserId)
60+
XCTAssertEqual(count, 2)
61+
}
62+
63+
func testDecisionListenerWithUserNotInExperimentAndRollout() {
64+
65+
self.optimizely.setDecisionServiceData(experiment: nil, variation: nil)
66+
self.optimizely.notificationCenter.clearAllNotificationListeners()
67+
_ = self.optimizely.notificationCenter.addDecisionNotificationListener { (type, userId, attributes, decisionInfo) in
68+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.featureEnabled] as! Bool, false)
69+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.source] as! String, Constants.DecisionSource.Rollout)
70+
XCTAssertNil(decisionInfo[Constants.DecisionInfoKeys.sourceExperiment])
71+
XCTAssertNil(decisionInfo[Constants.DecisionInfoKeys.sourceVariation])
72+
}
73+
_ = try? self.optimizely.isFeatureEnabled(featureKey: kFeatureKey, userId: kUserId)
74+
}
75+
76+
func testDecisionListenerWithUserInRollout() {
77+
78+
var variation: Variation = (self.optimizely.config?.allExperiments.first!.variations.first)!
79+
variation.featureEnabled = true
80+
self.optimizely.setDecisionServiceData(experiment: nil, variation: variation)
81+
self.optimizely.notificationCenter.clearAllNotificationListeners()
82+
_ = self.optimizely.notificationCenter.addDecisionNotificationListener { (type, userId, attributes, decisionInfo) in
83+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.featureEnabled] as! Bool, true)
84+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.source] as! String, Constants.DecisionSource.Rollout)
85+
XCTAssertNil(decisionInfo[Constants.DecisionInfoKeys.sourceExperiment])
86+
XCTAssertNil(decisionInfo[Constants.DecisionInfoKeys.sourceVariation])
87+
}
88+
_ = try! self.optimizely.isFeatureEnabled(featureKey: kFeatureKey, userId: kUserId)
89+
90+
variation.featureEnabled = false
91+
self.optimizely.setDecisionServiceData(experiment: nil, variation: variation)
92+
self.optimizely.notificationCenter.clearAllNotificationListeners()
93+
_ = self.optimizely.notificationCenter.addDecisionNotificationListener { (type, userId, attributes, decisionInfo) in
94+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.featureEnabled] as! Bool, false)
95+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.source] as! String, Constants.DecisionSource.Rollout)
96+
XCTAssertNil(decisionInfo[Constants.DecisionInfoKeys.sourceExperiment])
97+
XCTAssertNil(decisionInfo[Constants.DecisionInfoKeys.sourceVariation])
98+
}
99+
_ = try! self.optimizely.isFeatureEnabled(featureKey: kFeatureKey, userId: kUserId)
100+
}
101+
102+
func testDecisionListenerWithUserInExperiment() {
103+
104+
let experiment: Experiment = (self.optimizely.config?.allExperiments.first!)!
105+
var variation: Variation = (experiment.variations.first)!
106+
variation.featureEnabled = true
107+
self.optimizely.setDecisionServiceData(experiment: experiment, variation: variation)
108+
self.optimizely.notificationCenter.clearAllNotificationListeners()
109+
_ = self.optimizely.notificationCenter.addDecisionNotificationListener { (type, userId, attributes, decisionInfo) in
110+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.featureEnabled] as! Bool, true)
111+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.source] as! String, Constants.DecisionSource.Experiment)
112+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.sourceExperiment] as! String, experiment.key)
113+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.sourceVariation] as! String, variation.key)
114+
}
115+
_ = try! self.optimizely.isFeatureEnabled(featureKey: kFeatureKey, userId: kUserId)
116+
117+
variation.featureEnabled = false
118+
self.optimizely.setDecisionServiceData(experiment: experiment, variation: variation)
119+
self.optimizely.notificationCenter.clearAllNotificationListeners()
120+
_ = self.optimizely.notificationCenter.addDecisionNotificationListener { (type, userId, attributes, decisionInfo) in
121+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.featureEnabled] as! Bool, false)
122+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.source] as! String, Constants.DecisionSource.Experiment)
123+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.sourceExperiment] as! String, experiment.key)
124+
XCTAssertEqual(decisionInfo[Constants.DecisionInfoKeys.sourceVariation] as! String, variation.key)
125+
}
126+
_ = try! self.optimizely.isFeatureEnabled(featureKey: kFeatureKey, userId: kUserId)
127+
}
128+
23129
func testDecisionListenerWithActivateWhenUserInExperiment() {
24130
let attributes: [String: Any?] = ["s_foo": "foo",
25131
"b_true": "N/A",
@@ -46,7 +152,7 @@ class DecisionListenerTests: XCTestCase {
46152
XCTAssertEqual(notificationVariation, "all_traffic_variation")
47153
XCTAssertEqual(notificationType, Constants.DecisionTypeKeys.experiment)
48154
}
49-
155+
50156
func testDecisionListenerWithActivateWhenUserNotInExperiment() {
51157
let optimizely = OTUtils.createOptimizely(datafileName: "audience_targeting", clearUserProfileService: true)
52158
var notificationVariation : String?
@@ -58,11 +164,11 @@ class DecisionListenerTests: XCTestCase {
58164
notificationVariation = decisionInfo[Constants.NotificationKeys.variation] as? String
59165
notificationType = type
60166
})
61-
167+
62168
_ = try? optimizely?.activate(experimentKey:
63169
"ab_running_exp_audience_combo_exact_foo_or_true__and__42_or_4_2",
64-
userId: "test_user_1",
65-
attributes: nil)
170+
userId: "test_user_1",
171+
attributes: nil)
66172

67173
XCTAssertEqual(notificationExperiment, nil)
68174
XCTAssertEqual(notificationVariation, nil)
@@ -86,8 +192,8 @@ class DecisionListenerTests: XCTestCase {
86192
})
87193

88194
_ = try? optimizely?.getVariation(experimentKey: "ab_running_exp_audience_combo_empty_conditions",
89-
userId: "test_user_1",
90-
attributes: attributes)
195+
userId: "test_user_1",
196+
attributes: attributes)
91197

92198
XCTAssertEqual(notificationExperiment, "ab_running_exp_audience_combo_empty_conditions")
93199
XCTAssertEqual(notificationVariation, "all_traffic_variation")
@@ -112,5 +218,52 @@ class DecisionListenerTests: XCTestCase {
112218
XCTAssertEqual(notificationVariation, nil)
113219
XCTAssertEqual(notificationType, Constants.DecisionTypeKeys.experiment)
114220
}
221+
}
222+
223+
class FakeManager: OptimizelyManager {
224+
225+
override var decisionService: OPTDecisionService {
226+
get {
227+
return HandlerRegistryService.shared.injectDecisionService(sdkKey: self.sdkKey, isReintialize: true)!
228+
}
229+
}
230+
231+
override init(sdkKey: String, logger:OPTLogger? = nil, eventDispatcher:OPTEventDispatcher? = nil, userProfileService:OPTUserProfileService? = nil, periodicDownloadInterval:Int? = nil) {
232+
233+
super.init(sdkKey: sdkKey, logger: logger, eventDispatcher: eventDispatcher, userProfileService: userProfileService, periodicDownloadInterval: periodicDownloadInterval)
234+
HandlerRegistryService.shared.removeAll()
235+
236+
let userProfileService = userProfileService ?? DefaultUserProfileService()
237+
self.registerServices(sdkKey: sdkKey,
238+
logger: logger ?? DefaultLogger(),
239+
eventDispatcher: eventDispatcher ?? DefaultEventDispatcher.sharedInstance,
240+
datafileHandler: DefaultDatafileHandler(),
241+
decisionService: FakeDecisionService(userProfileService: userProfileService),
242+
notificationCenter: DefaultNotificationCenter())
243+
}
244+
245+
func setDecisionServiceData(experiment: Experiment?, variation: Variation?) {
246+
(self.decisionService as! FakeDecisionService).experiment = experiment
247+
(self.decisionService as! FakeDecisionService).variation = variation
248+
}
249+
}
115250

251+
class FakeDecisionService: DefaultDecisionService {
252+
253+
var experiment: Experiment?
254+
var variation: Variation?
255+
256+
override init(userProfileService: OPTUserProfileService) {
257+
super.init(userProfileService: DefaultUserProfileService())
258+
}
259+
260+
override func getVariationForFeature(config:ProjectConfig, featureFlag:FeatureFlag, userId:String, attributes: OptimizelyAttributes) -> (experiment:Experiment?, variation:Variation?)? {
261+
return (self.experiment, self.variation)
262+
}
263+
}
264+
265+
fileprivate extension HandlerRegistryService {
266+
func removeAll() {
267+
self.binders.removeAll()
268+
}
116269
}

OptimizelySDK/OptimizelyTests/OptimizelyTests-Common/NotificationCenterTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ class NotificationCenterTests: XCTestCase {
137137

138138
XCTAssertTrue(called)
139139
}
140-
140+
141141
func testNotificationCenterAddRemoveDecision() {
142142
// This is an example of a functional test case.
143143
// Use XCTAssert and related functions to verify your tests produce the correct results.

Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,4 @@ SPEC CHECKSUMS:
5757

5858
PODFILE CHECKSUM: 9e906f07df5c9e7952d1cd1a936498b4436bb865
5959

60-
COCOAPODS: 1.6.0
60+
COCOAPODS: 1.6.1

0 commit comments

Comments
 (0)