Skip to content

Commit 043d3f2

Browse files
Merge pull request #127 from optimizely/yasir/ondecision-activate-getvariation
2 parents ca7b467 + 4f270c8 commit 043d3f2

File tree

9 files changed

+248
-2
lines changed

9 files changed

+248
-2
lines changed

DemoSwiftApp/Customization/CustomNotificationCenter.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Foundation
1010
import Optimizely
1111

1212
class CustomNotificationCenter: OPTNotificationCenter {
13+
1314
func addFeatureFlagRolloutChangeListener(featureListener: @escaping FeatureFlagRolloutChangeListener) -> Int? {
1415
return nil
1516
}
@@ -53,6 +54,12 @@ class CustomNotificationCenter: OPTNotificationCenter {
5354
//
5455
return nil
5556
}
57+
58+
func addDecisionNotificationListener(decisionListener: @escaping DecisionListener) -> Int? {
59+
//
60+
return nil
61+
}
62+
5663
func addDatafileChangeNotificationListener(datafileListener: @escaping DatafileChangeListener) -> Int? {
5764
return nil
5865
}

OptimizelySDK/Implementation/DefaultNotificationCenter.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,26 @@ public class DefaultNotificationCenter : OPTNotificationCenter {
8484
return incrementNotificationId()
8585
}
8686

87+
public func addDecisionNotificationListener(decisionListener: @escaping (String, String, OptimizelyAttributes?, [String: Any]) -> Void) -> Int? {
88+
notificationListeners[notificationId] = (NotificationType.Decision.rawValue, { (args:Any...) in
89+
guard let myArgs = args[0] as? [Any?] else {
90+
return
91+
}
92+
if myArgs.count < 4 {
93+
return
94+
}
95+
if let type = myArgs[0] as? String,
96+
let userId = myArgs[1] as? String,
97+
let attributes = myArgs[2] as? OptimizelyAttributes?,
98+
let decisionInfo = myArgs[3] as? Dictionary<String,Any>
99+
{
100+
decisionListener(type, userId, attributes, decisionInfo)
101+
}
102+
})
103+
104+
return incrementNotificationId()
105+
}
106+
87107
public func addDatafileChangeNotificationListener(datafileListener: @escaping DatafileChangeListener) -> Int? {
88108
notificationListeners[notificationId] = (NotificationType.DatafileChange.rawValue, { (args:Any...) in
89109
guard let myArgs = args[0] as? [Any?] else {
@@ -137,5 +157,12 @@ public class DefaultNotificationCenter : OPTNotificationCenter {
137157
}
138158
}
139159

140-
160+
public func getArgumentsForDecisionListener(notificationType: String, userId: String, attributes: OptimizelyAttributes?) -> Array<Any?> {
161+
var args = Array<Any?>()
162+
args.append(notificationType)
163+
args.append(userId)
164+
args.append(attributes ?? OptimizelyAttributes())
165+
return args
166+
}
167+
141168
}

OptimizelySDK/Optimizely/OptimizelyManager.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,12 +323,25 @@ open class OptimizelyManager: NSObject {
323323
guard let experiment = config.getExperiment(key: experimentKey) else {
324324
throw OptimizelyError.experimentUnknown
325325
}
326+
327+
var args: Array<Any?> = (self.notificationCenter as! DefaultNotificationCenter).getArgumentsForDecisionListener(notificationType: Constants.DecisionTypeKeys.experiment, userId: userId, attributes: attributes)
328+
329+
var decisionInfo = Dictionary<String,Any>()
330+
decisionInfo[Constants.NotificationKeys.experiment] = nil
331+
decisionInfo[Constants.NotificationKeys.variation] = nil
326332

327333
// fix DecisionService to throw error
328334
guard let variation = decisionService.getVariation(config: config, userId: userId, experiment: experiment, attributes: attributes ?? OptimizelyAttributes()) else {
335+
args.append(decisionInfo)
336+
self.notificationCenter.sendNotifications(type: NotificationType.Decision.rawValue, args: args)
329337
throw OptimizelyError.variationUnknown
330338
}
331339

340+
decisionInfo[Constants.NotificationKeys.experiment] = experimentKey
341+
decisionInfo[Constants.NotificationKeys.variation] = variation.key
342+
args.append(decisionInfo)
343+
self.notificationCenter.sendNotifications(type: NotificationType.Decision.rawValue, args: args)
344+
332345
return variation
333346
}
334347

OptimizelySDK/OptimizelySwiftSDK.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,8 @@
10761076
6EF4B51C2239C7D9002DE8B6 /* BatchEventBuilderTests_Corners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF4B51B2239C7D9002DE8B6 /* BatchEventBuilderTests_Corners.swift */; };
10771077
6EF4B51D2239C7D9002DE8B6 /* BatchEventBuilderTests_Corners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF4B51B2239C7D9002DE8B6 /* BatchEventBuilderTests_Corners.swift */; };
10781078
825A305DA92576A5327962F4 /* Pods_OptimizelyTests_Common_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8093F8A983E68BDB49E29737 /* Pods_OptimizelyTests_Common_tvOS.framework */; };
1079+
C79F9D862251F58F002B0BC9 /* DecisionListenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79F9D852251F58F002B0BC9 /* DecisionListenerTests.swift */; };
1080+
C79F9D872251F597002B0BC9 /* DecisionListenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79F9D852251F58F002B0BC9 /* DecisionListenerTests.swift */; };
10791081
/* End PBXBuildFile section */
10801082

10811083
/* Begin PBXContainerItemProxy section */
@@ -1299,6 +1301,7 @@
12991301
7C392E11ABAA00DDA1F864B9 /* Pods_OptimizelyTests_APIs_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OptimizelyTests_APIs_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
13001302
8093F8A983E68BDB49E29737 /* Pods_OptimizelyTests_Common_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OptimizelyTests_Common_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
13011303
AED4B61B2F7C775E23D39EBB /* Pods-OptimizelyTests-Common-iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OptimizelyTests-Common-iOS.release.xcconfig"; path = "Target Support Files/Pods-OptimizelyTests-Common-iOS/Pods-OptimizelyTests-Common-iOS.release.xcconfig"; sourceTree = "<group>"; };
1304+
C79F9D852251F58F002B0BC9 /* DecisionListenerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecisionListenerTests.swift; sourceTree = "<group>"; };
13021305
EAF81B0DD7CB1C74984C7538 /* Pods-OptimizelyTests-Common-iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OptimizelyTests-Common-iOS.debug.xcconfig"; path = "Target Support Files/Pods-OptimizelyTests-Common-iOS/Pods-OptimizelyTests-Common-iOS.debug.xcconfig"; sourceTree = "<group>"; };
13031306
/* End PBXFileReference section */
13041307

@@ -1707,6 +1710,7 @@
17071710
0B894EBB224163FF004A2ADB /* EventDispatcherTest.swift */,
17081711
0BC47BD52242F7EF00E5C2CD /* DatafileHandlerTests.swift */,
17091712
0BC47BE42243012E00E5C2CD /* MurmurTests.swift */,
1713+
C79F9D852251F58F002B0BC9 /* DecisionListenerTests.swift */,
17101714
);
17111715
path = "OptimizelyTests-Common";
17121716
sourceTree = "<group>";
@@ -2881,6 +2885,7 @@
28812885
6EA4253E2218E4A300B074B5 /* Audience.swift in Sources */,
28822886
6EA425532218E4A300B074B5 /* Result.swift in Sources */,
28832887
0B77D05F22370526005AA83F /* BackgroundingCallbacks.swift in Sources */,
2888+
C79F9D872251F597002B0BC9 /* DecisionListenerTests.swift in Sources */,
28842889
6EA4254F2218E4A300B074B5 /* EventForDispatch+Extension.swift in Sources */,
28852890
6EA425262218E4A300B074B5 /* DefaultLogger.swift in Sources */,
28862891
6EA425282218E4A300B074B5 /* DefaultUserProfileService.swift in Sources */,
@@ -3357,6 +3362,7 @@
33573362
6E4DD8F321E530A900B0C2C7 /* OptimizelyResult.swift in Sources */,
33583363
6E4DD8A721E5260600B0C2C7 /* OPTUserProfileService.swift in Sources */,
33593364
6E4DD8A521E5260600B0C2C7 /* OPTEventDispatcher.swift in Sources */,
3365+
C79F9D862251F58F002B0BC9 /* DecisionListenerTests.swift in Sources */,
33603366
6E4DD8BD21E5261500B0C2C7 /* TrafficAllocation.swift in Sources */,
33613367
0B7B18882232DB8300A1F85D /* DataStoreMemory.swift in Sources */,
33623368
6EA4265122191EEC00B074B5 /* Array+Extension.swift in Sources */,
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
/****************************************************************************
3+
* Copyright 2019, Optimizely, Inc. and contributors *
4+
* *
5+
* Licensed under the Apache License, Version 2.0 (the "License"); *
6+
* you may not use this file except in compliance with the License. *
7+
* You may obtain a copy of the License at *
8+
* *
9+
* http://www.apache.org/licenses/LICENSE-2.0 *
10+
* *
11+
* Unless required by applicable law or agreed to in writing, software *
12+
* distributed under the License is distributed on an "AS IS" BASIS, *
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
14+
* See the License for the specific language governing permissions and *
15+
* limitations under the License. *
16+
***************************************************************************/
17+
18+
19+
import XCTest
20+
21+
class DecisionListenerTests: XCTestCase {
22+
23+
func testDecisionListenerWithActivateWhenUserInExperiment() {
24+
let attributes: [String: Any?] = ["s_foo": "foo",
25+
"b_true": "N/A",
26+
"i_42": 44,
27+
"d_4_2": "N/A"]
28+
let optimizely = OTUtils.createOptimizely(datafileName: "audience_targeting", clearUserProfileService: true)
29+
var notificationVariation : String?
30+
var notificationExperiment : String?
31+
var notificationType: String?
32+
33+
_ = optimizely?.notificationCenter.addDecisionNotificationListener(decisionListener: { (type, userId, attributes, decisionInfo) in
34+
notificationExperiment = decisionInfo[Constants.NotificationKeys.experiment] as? String
35+
notificationVariation = decisionInfo[Constants.NotificationKeys.variation] as? String
36+
notificationType = type
37+
})
38+
39+
let variation = try? optimizely?.activate(experimentKey:
40+
"ab_running_exp_audience_combo_empty_conditions",
41+
userId: "test_user_1",
42+
attributes: attributes)
43+
44+
XCTAssertEqual(variation, "all_traffic_variation")
45+
XCTAssertEqual(notificationExperiment, "ab_running_exp_audience_combo_empty_conditions")
46+
XCTAssertEqual(notificationVariation, "all_traffic_variation")
47+
XCTAssertEqual(notificationType, Constants.DecisionTypeKeys.experiment)
48+
}
49+
50+
func testDecisionListenerWithActivateWhenUserNotInExperiment() {
51+
let optimizely = OTUtils.createOptimizely(datafileName: "audience_targeting", clearUserProfileService: true)
52+
var notificationVariation : String?
53+
var notificationExperiment : String?
54+
var notificationType: String?
55+
56+
_ = optimizely?.notificationCenter.addDecisionNotificationListener(decisionListener: { (type, userId, attributes, decisionInfo) in
57+
notificationExperiment = decisionInfo[Constants.NotificationKeys.experiment] as? String
58+
notificationVariation = decisionInfo[Constants.NotificationKeys.variation] as? String
59+
notificationType = type
60+
})
61+
62+
_ = try? optimizely?.activate(experimentKey:
63+
"ab_running_exp_audience_combo_exact_foo_or_true__and__42_or_4_2",
64+
userId: "test_user_1",
65+
attributes: nil)
66+
67+
XCTAssertEqual(notificationExperiment, nil)
68+
XCTAssertEqual(notificationVariation, nil)
69+
XCTAssertEqual(notificationType, Constants.DecisionTypeKeys.experiment)
70+
}
71+
72+
func testDecisionListenerWithGetVariationWhenUserInExperiment() {
73+
let attributes: [String: Any?] = ["s_foo": "foo",
74+
"b_true": "N/A",
75+
"i_42": 44,
76+
"d_4_2": "N/A"]
77+
let optimizely = OTUtils.createOptimizely(datafileName: "audience_targeting", clearUserProfileService: true)
78+
var notificationVariation : String?
79+
var notificationExperiment : String?
80+
var notificationType: String?
81+
82+
_ = optimizely?.notificationCenter.addDecisionNotificationListener(decisionListener: { (type, userId, _attributes, decisionInfo) in
83+
notificationExperiment = decisionInfo[Constants.NotificationKeys.experiment] as? String
84+
notificationVariation = decisionInfo[Constants.NotificationKeys.variation] as? String
85+
notificationType = type
86+
})
87+
88+
_ = try? optimizely?.getVariation(experimentKey: "ab_running_exp_audience_combo_empty_conditions",
89+
userId: "test_user_1",
90+
attributes: attributes)
91+
92+
XCTAssertEqual(notificationExperiment, "ab_running_exp_audience_combo_empty_conditions")
93+
XCTAssertEqual(notificationVariation, "all_traffic_variation")
94+
XCTAssertEqual(notificationType, Constants.DecisionTypeKeys.experiment)
95+
}
96+
97+
func testDecisionListenerWithGetVariationWhenUserNotInExperiment() {
98+
let optimizely = OTUtils.createOptimizely(datafileName: "audience_targeting", clearUserProfileService: true)
99+
var notificationVariation : String?
100+
var notificationExperiment : String?
101+
var notificationType: String?
102+
103+
_ = optimizely?.notificationCenter.addDecisionNotificationListener(decisionListener: { (type, userId, attributes, decisionInfo) in
104+
notificationExperiment = decisionInfo[Constants.NotificationKeys.experiment] as? String
105+
notificationVariation = decisionInfo[Constants.NotificationKeys.variation] as? String
106+
notificationType = type
107+
})
108+
109+
_ = try? optimizely?.getVariation(experimentKey: "ab_running_exp_audience_combo_exact_foo_or_true__and__42_or_4_2", userId: "test_user_1")
110+
111+
XCTAssertEqual(notificationExperiment, nil)
112+
XCTAssertEqual(notificationVariation, nil)
113+
XCTAssertEqual(notificationType, Constants.DecisionTypeKeys.experiment)
114+
}
115+
116+
}

OptimizelySDK/OptimizelyTests/OptimizelyTests-Common/NotificationCenterTests.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ class NotificationCenterTests: XCTestCase {
5454
notificationCenter.sendNotifications(type: NotificationType.Track.rawValue, args: ["eventKey", "userId", nil, nil, ["url":"https://url.com/", "body": Data()]])
5555

5656
}
57+
58+
func sendDecision() {
59+
notificationCenter.sendNotifications(type: NotificationType.Decision.rawValue, args: [Constants.DecisionTypeKeys.experiment, "userId", nil, ["url":"https://url.com/", "body": Data()]])
60+
61+
}
5762

5863
func sendDatafileChange() {
5964
notificationCenter.sendNotifications(type: NotificationType.DatafileChange.rawValue, args: [Data()])
@@ -133,6 +138,40 @@ class NotificationCenterTests: XCTestCase {
133138
XCTAssertTrue(called)
134139
}
135140

141+
func testNotificationCenterAddRemoveDecision() {
142+
// This is an example of a functional test case.
143+
// Use XCTAssert and related functions to verify your tests produce the correct results.
144+
var called = false
145+
146+
let _ = notificationCenter.addDecisionNotificationListener { (type, userId, attr, decisionInfo) in
147+
called = true
148+
}
149+
150+
notificationCenter.clearNotificationListeners(type: .Decision)
151+
152+
sendDecision()
153+
154+
XCTAssertFalse(called)
155+
156+
let id = notificationCenter.addDecisionNotificationListener { (type, userId, attr, decisionInfo) in
157+
called = true
158+
}
159+
160+
notificationCenter.removeNotificationListener(notificationId: id!)
161+
162+
sendDecision()
163+
164+
XCTAssertFalse(called)
165+
166+
let _ = notificationCenter.addDecisionNotificationListener { (type, userId, attr, decisionInfo) in
167+
called = true
168+
}
169+
170+
sendDecision()
171+
172+
XCTAssertTrue(called)
173+
}
174+
136175
func testNotificationCenterAddRemoveDatafileChange() {
137176
// This is an example of a functional test case.
138177
// Use XCTAssert and related functions to verify your tests produce the correct results.

OptimizelySDK/Protocols/OPTNotificationCenter.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public enum NotificationType : Int {
2222
case Track
2323
case DatafileChange
2424
case FeatureFlagRolloutToggle
25+
case Decision
2526
}
2627

2728
public enum FeatureFlagToggle {
@@ -41,6 +42,8 @@ public typealias ActivateListener = (_ experiment:OptimizelyExperimentData, _ us
4142

4243
public typealias TrackListener = (_ eventKey:String, _ userId:String, _ attributes: OptimizelyAttributes?, _ eventTags:Dictionary<String, Any>?, _ event:Dictionary<String, Any>) -> Void
4344

45+
public typealias DecisionListener = (_ type:String, _ userId:String, _ attributes: OptimizelyAttributes?, _ decisionInfo:Dictionary<String, Any>) -> Void
46+
4447
public typealias DatafileChangeListener = (_ datafile:Data) -> Void
4548

4649
public typealias FeatureFlagRolloutChangeListener = (_ featureFlagKey:String, _ toggle:FeatureFlagToggle ) -> Void
@@ -72,6 +75,13 @@ func addActivateNotificationListener(activateListener:@escaping ActivateListener
7275
*/
7376
func addTrackNotificationListener(trackListener:@escaping TrackListener) -> Int?
7477

78+
/**
79+
Add a decision notification listener to the notification center.
80+
- Parameter decisionListener: Notification to add.
81+
- Returns: the notification id used to remove the notification. It is greater than 0 on success.
82+
*/
83+
func addDecisionNotificationListener(decisionListener:@escaping DecisionListener) -> Int?
84+
7585
/**
7686
Add a datafile change notification listener
7787
- Parameter datafileChangeListener: Notification to add.

OptimizelySDK/Utils/Constants.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,32 @@ struct Constants {
1414
static let OptimizelyBotFilteringAttribute = "$opt_bot_filtering";
1515
static let OptimizelyUserAgent = "$opt_user_agent";
1616
}
17+
18+
struct NotificationKeys {
19+
static let experiment = "experiment"
20+
static let variation = "variation"
21+
}
22+
23+
struct DecisionTypeKeys {
24+
static let featureVariable = "feature_variable"
25+
static let isFeatureEnabled = "feature"
26+
static let experiment = "experiment"
27+
}
28+
29+
struct DecisionInfoKeys {
30+
static let feature = "featureKey"
31+
static let featureEnabled = "featureEnabled"
32+
static let sourceExperiment = "sourceExperimentKey"
33+
static let sourceVariation = "sourceVariationKey"
34+
static let source = "source"
35+
static let variable = "variableKey"
36+
static let variableType = "variableType"
37+
static let variableValue = "variableValue"
38+
}
39+
40+
struct DecisionSource {
41+
static let Experiment = "EXPERIMENT"
42+
static let Rollout = "ROLLOUT"
43+
}
44+
1745
}

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: 4f4ce5504801dbc19d61fee45d7740aa732a995c
5959

60-
COCOAPODS: 1.6.1
60+
COCOAPODS: 1.6.0

0 commit comments

Comments
 (0)