Skip to content

Commit 72d4b60

Browse files
(feat): OnDecision Notification Listener for Activate/getVariation API (#386)
* 1. onDecision listener implemented for activate/getVariation. 2. Removed redundant keys used for validation purposes. * Unit tests to verify empty attributes by default in decision listener. * Bug fixes. * Implementations according to javascript PR review. * Headers updated. * Misspells fixed. * 1. Auto-generated files updated. 2. Recommended changes made. * Reverting changes to travis.yml.
1 parent 1caa63d commit 72d4b60

File tree

10 files changed

+166
-12
lines changed

10 files changed

+166
-12
lines changed

OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ typedef void (^ActivateListener)(OPTLYExperiment * _Nonnull experiment,
2929
NSString * _Nonnull userId,
3030
NSDictionary<NSString *, id> * _Nullable attributes,
3131
OPTLYVariation * _Nonnull variation,
32-
NSDictionary<NSString *,id> * _Nonnull event);
32+
NSDictionary<NSString *,id> * _Nonnull event) __deprecated;
3333

3434
typedef void (^TrackListener)(NSString * _Nonnull eventKey,
3535
NSString * _Nonnull userId,
@@ -67,6 +67,7 @@ struct DecisionInfoStruct {
6767
extern const struct DecisionInfoStruct DecisionInfo;
6868

6969
/// Notification decision types.
70+
extern NSString * _Nonnull const OPTLYDecisionTypeExperiment;
7071
extern NSString * _Nonnull const OPTLYDecisionTypeIsFeatureEnabled;
7172

7273
@interface OPTLYNotificationCenter : NSObject
@@ -88,7 +89,7 @@ extern NSString * _Nonnull const OPTLYDecisionTypeIsFeatureEnabled;
8889
* @param activateListener - Notification to add.
8990
* @return the notification id used to remove the notification. It is greater than 0 on success.
9091
*/
91-
- (NSInteger)addActivateNotificationListener:(nonnull ActivateListener)activateListener;
92+
- (NSInteger)addActivateNotificationListener:(nonnull ActivateListener)activateListener __deprecated_msg("Use DecisionListener instead");
9293

9394
/**
9495
* Add a track notification listener to the notification center.

OptimizelySDKCore/OptimizelySDKCore/OPTLYNotificationCenter.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
};
4343

4444
/// Notification decision types.
45+
NSString * _Nonnull const OPTLYDecisionTypeExperiment = @"experiment";
4546
NSString * _Nonnull const OPTLYDecisionTypeIsFeatureEnabled = @"feature";
4647

4748
@interface OPTLYNotificationCenter()

OptimizelySDKCore/OptimizelySDKCore/Optimizely.m

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,18 @@ - (OPTLYVariation *)variation:(NSString *)experimentKey
177177
attributes:attributes
178178
bucketer:self.bucketer];
179179

180+
NSMutableDictionary *args = [[NSMutableDictionary alloc] init];
181+
[args setValue:OPTLYDecisionTypeExperiment forKey:OPTLYNotificationDecisionTypeKey];
182+
[args setValue:userId forKey:OPTLYNotificationUserIdKey];
183+
[args setValue:attributes forKey:OPTLYNotificationAttributesKey];
184+
185+
NSMutableDictionary *decisionInfo = [NSMutableDictionary new];
186+
[decisionInfo setValue:(bucketedVariation.variationKey ? experimentKey : [NSNull null]) forKey:OPTLYNotificationExperimentKey];
187+
[decisionInfo setValue:(bucketedVariation.variationKey ?: [NSNull null]) forKey:OPTLYNotificationVariationKey];
188+
[args setValue:decisionInfo forKey:DecisionInfo.Key];
189+
190+
[_notificationCenter sendNotifications:OPTLYNotificationTypeDecision args:args];
191+
180192
return bucketedVariation;
181193
}
182194

@@ -407,7 +419,7 @@ - (NSString *)getFeatureVariableString:(nullable NSString *)featureKey
407419
NSMutableArray<NSString *> *enabledFeatures = [NSMutableArray new];
408420

409421
NSMutableDictionary<NSString *, NSString *> *inputValues = [[NSMutableDictionary alloc] initWithDictionary:@{
410-
OPTLYNotificationUserIdKey:[self ObjectOrNull:userId]}];
422+
OPTLYNotificationUserIdKey:[self ObjectOrNull:userId]}];
411423
NSDictionary <NSString *, NSString *> *logs = @{};
412424

413425
if (![self validateStringInputs:inputValues logs:logs]) {

OptimizelySDKCore/OptimizelySDKCoreTests/OptimizelyTest.m

Lines changed: 143 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,9 @@ - (void)testBasicGetVariation {
169169
XCTAssertTrue([variation.variationId isEqualToString:@"6384330451"]);
170170

171171
// test with bad experiment key
172+
172173
variation = [self.optimizely variation:@"bad" userId:kUserId];
173174
XCTAssertNil(variation);
174-
175175
}
176176

177177
- (void)testVariationWithAudience {
@@ -246,6 +246,62 @@ - (void)testVariationWhitelisting {
246246
XCTAssertEqualObjects(variation.variationKey, kVariationKeyForWhitelisting);
247247
}
248248

249+
#pragma mark - Get Variation <DECISION NOTIFICATION> Tests
250+
251+
- (void)testDecisionNotificationForBasicGetVariation {
252+
NSString *experimentKey = @"testExperiment1";
253+
OPTLYExperiment *experiment = [self.optimizely.config getExperimentForKey:experimentKey];
254+
255+
XCTAssertNotNil(experiment);
256+
OPTLYVariation *variation;
257+
258+
// test just experiment key
259+
variation = [self.optimizely variation:experimentKey userId:kUserId];
260+
XCTAssertNotNil(variation);
261+
XCTAssertTrue([variation.variationKey isEqualToString:@"control"]);
262+
XCTAssertTrue([variation.variationId isEqualToString:@"6384330451"]);
263+
264+
// test with bad experiment key
265+
266+
__block NSString *decisionNotificationExperimentKey = nil;
267+
__block NSString *decisionNotificationVariationKey = nil;
268+
269+
[self.optimizely.notificationCenter addDecisionNotificationListener:^(NSString * _Nonnull type, NSString * _Nonnull userId, NSDictionary<NSString *,id> * _Nullable attributes, NSDictionary<NSString *,id> * _Nonnull decisionInfo) {
270+
decisionNotificationExperimentKey = decisionInfo[OPTLYNotificationExperimentKey];
271+
decisionNotificationVariationKey = decisionInfo[OPTLYNotificationVariationKey];
272+
}];
273+
274+
variation = [self.optimizely variation:@"bad" userId:kUserId];
275+
XCTAssertNil(variation);
276+
XCTAssertEqualObjects(decisionNotificationExperimentKey, [NSNull null]);
277+
XCTAssertEqualObjects(decisionNotificationVariationKey, [NSNull null]);
278+
}
279+
280+
// Test whitelisting works with get variation
281+
- (void)testDecisionNotificationForVariationWhitelisting {
282+
NSData *datafile = [OPTLYTestHelper loadJSONDatafileIntoDataObject:kBucketerTestDatafileName];
283+
__block NSString *decisionNotificationExperimentKey = nil;
284+
__block NSString *decisionNotificationVariationKey = nil;
285+
286+
Optimizely *optimizely = [[Optimizely alloc] initWithBuilder:[OPTLYBuilder builderWithBlock:^(OPTLYBuilder * _Nullable builder) {
287+
builder.datafile = datafile;
288+
}]];
289+
XCTAssertNotNil(optimizely);
290+
291+
[optimizely.notificationCenter addDecisionNotificationListener:^(NSString * _Nonnull type, NSString * _Nonnull userId, NSDictionary<NSString *,id> * _Nullable attributes, NSDictionary<NSString *,id> * _Nonnull decisionInfo) {
292+
decisionNotificationExperimentKey = decisionInfo[OPTLYNotificationExperimentKey];
293+
decisionNotificationVariationKey = decisionInfo[OPTLYNotificationVariationKey];
294+
}];
295+
296+
// get variation
297+
OPTLYVariation *variation = [optimizely variation:kExperimentKeyForWhitelisting userId:kUserIdForWhitelisting];
298+
XCTAssertNotNil(variation);
299+
XCTAssertEqualObjects(variation.variationId, kVariationIDForWhitelisting);
300+
XCTAssertEqualObjects(variation.variationKey, kVariationKeyForWhitelisting);
301+
XCTAssertEqualObjects(decisionNotificationExperimentKey, kExperimentKeyForWhitelisting);
302+
XCTAssertEqualObjects(decisionNotificationVariationKey, variation.variationKey);
303+
}
304+
249305
# pragma mark - Integration Tests
250306

251307
- (void)testOptimizelyActivateWithEmptyUserId {
@@ -286,6 +342,7 @@ - (void)testOptimizelyActivateWithInvalidExperiment {
286342
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"getActivatedVariation"];
287343

288344
NSString *invalidExperimentKey = @"invalid";
345+
289346
OPTLYVariation *variation = [self.optimizely activate:invalidExperimentKey userId:kUserId attributes:self.attributes callback:^(NSError *error) {
290347
XCTAssertNotNil(error);
291348
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesActivateExperimentKeyInvalid, invalidExperimentKey];
@@ -342,7 +399,6 @@ - (void)testOptimizelyActivateWithNonTargetingAudience {
342399
[expectation fulfill];
343400
}];
344401
XCTAssertNil(variation);
345-
346402
[self waitForExpectationsWithTimeout:2 handler:nil];
347403
}
348404

@@ -385,7 +441,7 @@ - (void)testOptimizelyPostsActivateExperimentNotificationAllAttributes {
385441
XCTAssertEqual(experiment.experimentId, notificationExperimentKey);
386442
}
387443

388-
- (void)testOptimizelyPostsActivateExperimentNotificationNilAttributes {
444+
- (void)testOptimizelyPostsActivateExperimentNotificationEmptyAttributes {
389445

390446
OPTLYExperiment *experiment = [self.optimizely.config getExperimentForKey:kExperimentKeyForWhitelisting];
391447
__block NSString *notificationExperimentKey = nil;
@@ -398,12 +454,10 @@ - (void)testOptimizelyPostsActivateExperimentNotificationNilAttributes {
398454

399455
OPTLYVariation *_variation = [self.optimizely activate:kExperimentKeyForWhitelisting
400456
userId:kUserId attributes:nil];
401-
XCTAssertNil(actualAttributes);
402457
XCTAssertNotNil(_variation);
403458
XCTAssertEqual(experiment.experimentId, notificationExperimentKey);
404459
}
405460

406-
407461
- (void)testOptimizelyTrackWithNoEvent {
408462

409463
NSString *eventKey;
@@ -563,6 +617,90 @@ - (void)testOptimizelyPostEventTrackNotificationWithEventTags {
563617
XCTAssertEqual(eventTags, notificationEventTags);
564618
}
565619

620+
#pragma mark - Activate <DECISION NOTIFICATION> Tests
621+
622+
- (void)testDecisionNotificationForActivateWithNonTargetingAudience {
623+
624+
__weak XCTestExpectation *expectation = [self expectationWithDescription:@"getActivatedVariation"];
625+
__block NSString *decisionNotificationVariationKey = nil;
626+
627+
[self.optimizely.notificationCenter addDecisionNotificationListener:^(NSString * _Nonnull type, NSString * _Nonnull userId, NSDictionary<NSString *,id> * _Nullable attributes, NSDictionary<NSString *,id> * _Nonnull decisionInfo) {
628+
decisionNotificationVariationKey = decisionInfo[OPTLYNotificationVariationKey];
629+
}];
630+
631+
OPTLYVariation *variation = [self.optimizely activate:kExperimentKey
632+
userId:kUserId
633+
attributes:nil callback:^(NSError *error) {
634+
XCTAssertNotNil(error);
635+
NSString *logMessage = [NSString stringWithFormat:OPTLYLoggerMessagesEventDispatcherActivationFailure, kUserId, kExperimentKey];
636+
XCTAssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey], logMessage);
637+
[expectation fulfill];
638+
}];
639+
XCTAssertNil(variation);
640+
XCTAssertEqualObjects(decisionNotificationVariationKey, [NSNull null]);
641+
[self waitForExpectationsWithTimeout:2 handler:nil];
642+
}
643+
644+
- (void)testOptimizelyPostsOnDecisionActivateNotification {
645+
646+
OPTLYExperiment *experiment = [self.optimizely.config getExperimentForKey:kExperimentKeyForWhitelisting];
647+
__block NSString *decisionNotificationExperimentKey = nil;
648+
__block NSString *decisionNotificationVariationKey = nil;
649+
650+
[self.optimizely.notificationCenter addDecisionNotificationListener:^(NSString * _Nonnull type, NSString * _Nonnull userId, NSDictionary<NSString *,id> * _Nullable attributes, NSDictionary<NSString *,id> * _Nonnull decisionInfo) {
651+
decisionNotificationExperimentKey = decisionInfo[OPTLYNotificationExperimentKey];
652+
decisionNotificationVariationKey = decisionInfo[OPTLYNotificationVariationKey];
653+
}];
654+
655+
OPTLYVariation *_variation = [self.optimizely activate:kExperimentKeyForWhitelisting
656+
userId:kUserId];
657+
XCTAssertNotNil(_variation);
658+
XCTAssertEqualObjects(experiment.experimentKey, decisionNotificationExperimentKey);
659+
XCTAssertEqualObjects(_variation.variationKey, decisionNotificationVariationKey);
660+
}
661+
662+
- (void)testOptimizelyPostsOnDecisionActivateNotificationAllAttributes {
663+
664+
OPTLYExperiment *experiment = [self.optimizely.config getExperimentForKey:kExperimentKeyForWhitelisting];
665+
__block NSString *decisionNotificationExperimentKey = nil;
666+
667+
NSDictionary<NSString *, id> *expectedAttributes = @{
668+
@"browser_name": @"chrome",
669+
@"buildno": @(10),
670+
@"buildversion": @(0.13)
671+
};
672+
__block NSDictionary<NSString *, id> *decisionActualAttributes;
673+
674+
[self.optimizely.notificationCenter addDecisionNotificationListener:^(NSString * _Nonnull type, NSString * _Nonnull userId, NSDictionary<NSString *,id> * _Nullable attributes, NSDictionary<NSString *,id> * _Nonnull decisionInfo) {
675+
decisionNotificationExperimentKey = decisionInfo[OPTLYNotificationExperimentKey];
676+
decisionActualAttributes = attributes;
677+
}];
678+
679+
OPTLYVariation *_variation = [self.optimizely activate:kExperimentKeyForWhitelisting
680+
userId:kUserId attributes:expectedAttributes];
681+
XCTAssertEqualObjects(expectedAttributes, decisionActualAttributes);
682+
XCTAssertNotNil(_variation);
683+
XCTAssertEqualObjects(experiment.experimentKey, decisionNotificationExperimentKey);
684+
}
685+
686+
- (void)testOptimizelyPostsPostsOnDecisionActivateNotificationEmptyAttributes {
687+
688+
OPTLYExperiment *experiment = [self.optimizely.config getExperimentForKey:kExperimentKeyForWhitelisting];
689+
__block NSString *decisionNotificationExperimentKey = nil;
690+
__block NSDictionary<NSString *, id> *decisionActualAttributes;
691+
692+
[self.optimizely.notificationCenter addDecisionNotificationListener:^(NSString * _Nonnull type, NSString * _Nonnull userId, NSDictionary<NSString *,id> * _Nullable attributes, NSDictionary<NSString *,id> * _Nonnull decisionInfo) {
693+
decisionNotificationExperimentKey = decisionInfo[OPTLYNotificationExperimentKey];
694+
decisionActualAttributes = attributes;
695+
}];
696+
697+
OPTLYVariation *_variation = [self.optimizely activate:kExperimentKeyForWhitelisting
698+
userId:kUserId attributes:nil];
699+
XCTAssertEqualObjects(decisionActualAttributes, @{});
700+
XCTAssertNotNil(_variation);
701+
XCTAssertEqualObjects(experiment.experimentKey, decisionNotificationExperimentKey);
702+
}
703+
566704
# pragma mark - IsFeatureEnabled Tests
567705

568706
// Should return false when arguments are nil or empty.

OptimizelySDKUniversal/generated-frameworks/Release-iOS-universal-SDK/OptimizelySDKiOS.framework/Headers/OPTLYNotificationCenter.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ typedef void (^ActivateListener)(OPTLYExperiment * _Nonnull experiment,
2929
NSString * _Nonnull userId,
3030
NSDictionary<NSString *, id> * _Nullable attributes,
3131
OPTLYVariation * _Nonnull variation,
32-
NSDictionary<NSString *,id> * _Nonnull event);
32+
NSDictionary<NSString *,id> * _Nonnull event) __deprecated;
3333

3434
typedef void (^TrackListener)(NSString * _Nonnull eventKey,
3535
NSString * _Nonnull userId,
@@ -67,6 +67,7 @@ struct DecisionInfoStruct {
6767
extern const struct DecisionInfoStruct DecisionInfo;
6868

6969
/// Notification decision types.
70+
extern NSString * _Nonnull const OPTLYDecisionTypeExperiment;
7071
extern NSString * _Nonnull const OPTLYDecisionTypeIsFeatureEnabled;
7172

7273
@interface OPTLYNotificationCenter : NSObject
@@ -88,7 +89,7 @@ extern NSString * _Nonnull const OPTLYDecisionTypeIsFeatureEnabled;
8889
* @param activateListener - Notification to add.
8990
* @return the notification id used to remove the notification. It is greater than 0 on success.
9091
*/
91-
- (NSInteger)addActivateNotificationListener:(nonnull ActivateListener)activateListener;
92+
- (NSInteger)addActivateNotificationListener:(nonnull ActivateListener)activateListener __deprecated_msg("Use DecisionListener instead");
9293

9394
/**
9495
* Add a track notification listener to the notification center.

OptimizelySDKUniversal/generated-frameworks/Release-tvOS-universal-SDK/OptimizelySDKTVOS.framework/Headers/OPTLYNotificationCenter.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ typedef void (^ActivateListener)(OPTLYExperiment * _Nonnull experiment,
2929
NSString * _Nonnull userId,
3030
NSDictionary<NSString *, id> * _Nullable attributes,
3131
OPTLYVariation * _Nonnull variation,
32-
NSDictionary<NSString *,id> * _Nonnull event);
32+
NSDictionary<NSString *,id> * _Nonnull event) __deprecated;
3333

3434
typedef void (^TrackListener)(NSString * _Nonnull eventKey,
3535
NSString * _Nonnull userId,
@@ -67,6 +67,7 @@ struct DecisionInfoStruct {
6767
extern const struct DecisionInfoStruct DecisionInfo;
6868

6969
/// Notification decision types.
70+
extern NSString * _Nonnull const OPTLYDecisionTypeExperiment;
7071
extern NSString * _Nonnull const OPTLYDecisionTypeIsFeatureEnabled;
7172

7273
@interface OPTLYNotificationCenter : NSObject
@@ -88,7 +89,7 @@ extern NSString * _Nonnull const OPTLYDecisionTypeIsFeatureEnabled;
8889
* @param activateListener - Notification to add.
8990
* @return the notification id used to remove the notification. It is greater than 0 on success.
9091
*/
91-
- (NSInteger)addActivateNotificationListener:(nonnull ActivateListener)activateListener;
92+
- (NSInteger)addActivateNotificationListener:(nonnull ActivateListener)activateListener __deprecated_msg("Use DecisionListener instead");
9293

9394
/**
9495
* Add a track notification listener to the notification center.

0 commit comments

Comments
 (0)