Skip to content

Commit 1edba1f

Browse files
mfahadahmedaliabbasrizvi
authored andcommitted
feat(api): Decision Notification Listener for activate and getVariation APIs. (#244)
1 parent 553e6ff commit 1edba1f

File tree

3 files changed

+139
-36
lines changed

3 files changed

+139
-36
lines changed

packages/optimizely-sdk/lib/optimizely/index.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ var LOG_MESSAGES = enums.LOG_MESSAGES;
3434
var MODULE_NAME = 'OPTIMIZELY';
3535
var DECISION_SOURCES = enums.DECISION_SOURCES;
3636
var FEATURE_VARIABLE_TYPES = enums.FEATURE_VARIABLE_TYPES;
37+
var DECISION_INFO_TYPES = enums.DECISION_INFO_TYPES;
38+
var NOTIFICATION_TYPES = enums.NOTIFICATION_TYPES;
3739

3840
/**
3941
* The Optimizely class
@@ -194,7 +196,7 @@ Optimizely.prototype._sendImpressionEvent = function(experimentKey, variationKey
194196
variation = experiment.variationKeyMap[variationKey];
195197
}
196198
this.notificationCenter.sendNotifications(
197-
enums.NOTIFICATION_TYPES.ACTIVATE,
199+
NOTIFICATION_TYPES.ACTIVATE,
198200
{
199201
experiment: experiment,
200202
userId: userId,
@@ -257,7 +259,7 @@ Optimizely.prototype.track = function(eventKey, userId, attributes, eventTags) {
257259
this.__dispatchEvent(conversionEvent, eventDispatcherCallback);
258260

259261
this.notificationCenter.sendNotifications(
260-
enums.NOTIFICATION_TYPES.TRACK,
262+
NOTIFICATION_TYPES.TRACK,
261263
{
262264
eventKey: eventKey,
263265
userId: userId,
@@ -304,7 +306,21 @@ Optimizely.prototype.getVariation = function(experimentKey, userId, attributes)
304306
return null;
305307
}
306308

307-
return this.decisionService.getVariation(experimentKey, userId, attributes);
309+
var variationKey = this.decisionService.getVariation(experimentKey, userId, attributes);
310+
this.notificationCenter.sendNotifications(
311+
NOTIFICATION_TYPES.DECISION,
312+
{
313+
type: DECISION_INFO_TYPES.EXPERIMENT,
314+
userId: userId,
315+
attributes: attributes || {},
316+
decisionInfo: {
317+
experimentKey: experimentKey,
318+
variationKey: variationKey,
319+
}
320+
}
321+
);
322+
323+
return variationKey;
308324
} catch (ex) {
309325
this.logger.log(LOG_LEVEL.ERROR, ex.message);
310326
this.errorHandler.handleError(ex);

packages/optimizely-sdk/lib/optimizely/index.tests.js

Lines changed: 107 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var ERROR_MESSAGES = enums.ERROR_MESSAGES;
4040
var LOG_LEVEL = enums.LOG_LEVEL;
4141
var LOG_MESSAGES = enums.LOG_MESSAGES;
4242
var DECISION_SOURCES = enums.DECISION_SOURCES;
43+
var DECISION_INFO_TYPES = enums.DECISION_INFO_TYPES;
4344

4445
describe('lib/optimizely', function() {
4546
describe('constructor', function() {
@@ -356,7 +357,6 @@ describe('lib/optimizely', function() {
356357
var optlyInstance;
357358
var bucketStub;
358359
var clock;
359-
360360
var createdLogger = logger.createLogger({
361361
logLevel: LOG_LEVEL.INFO,
362362
logToConsole: false,
@@ -394,8 +394,8 @@ describe('lib/optimizely', function() {
394394
describe('#activate', function() {
395395
it('should call bucketer and dispatchEvent with proper args and return variation key', function() {
396396
bucketStub.returns('111129');
397-
var activate = optlyInstance.activate('testExperiment', 'testUser');
398-
assert.strictEqual(activate, 'variation');
397+
var variation = optlyInstance.activate('testExperiment', 'testUser');
398+
assert.strictEqual(variation, 'variation');
399399

400400
sinon.assert.calledOnce(bucketer.bucket);
401401
sinon.assert.calledOnce(eventDispatcher.dispatchEvent);
@@ -1622,9 +1622,9 @@ describe('lib/optimizely', function() {
16221622
describe('#getVariation', function() {
16231623
it('should call bucketer and return variation key', function() {
16241624
bucketStub.returns('111129');
1625-
var getVariation = optlyInstance.getVariation('testExperiment', 'testUser');
1625+
var variation = optlyInstance.getVariation('testExperiment', 'testUser');
16261626

1627-
assert.strictEqual(getVariation, 'variation');
1627+
assert.strictEqual(variation, 'variation');
16281628

16291629
sinon.assert.calledOnce(bucketer.bucket);
16301630
sinon.assert.called(createdLogger.log);
@@ -2064,15 +2064,15 @@ describe('lib/optimizely', function() {
20642064
});
20652065

20662066
describe('notification listeners', function() {
2067-
var decisionListener;
2067+
var activateListener;
20682068
var trackListener;
2069-
var decisionListener2;
2069+
var activateListener2;
20702070
var trackListener2;
20712071

20722072
beforeEach(function() {
2073-
decisionListener = sinon.spy();
2073+
activateListener = sinon.spy();
20742074
trackListener = sinon.spy();
2075-
decisionListener2 = sinon.spy();
2075+
activateListener2 = sinon.spy();
20762076
trackListener2 = sinon.spy();
20772077
bucketStub.returns('111129');
20782078
sinon.stub(fns, 'currentTimestamp').returns(1509489766569);
@@ -2085,11 +2085,11 @@ describe('lib/optimizely', function() {
20852085
it('should call a listener added for activate when activate is called', function() {
20862086
optlyInstance.notificationCenter.addNotificationListener(
20872087
enums.NOTIFICATION_TYPES.ACTIVATE,
2088-
decisionListener
2088+
activateListener
20892089
);
20902090
var variationKey = optlyInstance.activate('testExperiment', 'testUser');
20912091
assert.strictEqual(variationKey, 'variation');
2092-
sinon.assert.calledOnce(decisionListener);
2092+
sinon.assert.calledOnce(activateListener);
20932093
});
20942094

20952095
it('should call a listener added for track when track is called', function() {
@@ -2105,12 +2105,12 @@ describe('lib/optimizely', function() {
21052105
it('should not call a removed activate listener when activate is called', function() {
21062106
var listenerId = optlyInstance.notificationCenter.addNotificationListener(
21072107
enums.NOTIFICATION_TYPES.ACTIVATE,
2108-
decisionListener
2108+
activateListener
21092109
);
21102110
optlyInstance.notificationCenter.removeNotificationListener(listenerId);
21112111
var variationKey = optlyInstance.activate('testExperiment', 'testUser');
21122112
assert.strictEqual(variationKey, 'variation');
2113-
sinon.assert.notCalled(decisionListener);
2113+
sinon.assert.notCalled(activateListener);
21142114
});
21152115

21162116
it('should not call a removed track listener when track is called', function() {
@@ -2127,7 +2127,7 @@ describe('lib/optimizely', function() {
21272127
it('removeNotificationListener should only remove the listener with the argument ID', function() {
21282128
optlyInstance.notificationCenter.addNotificationListener(
21292129
enums.NOTIFICATION_TYPES.ACTIVATE,
2130-
decisionListener
2130+
activateListener
21312131
);
21322132
var trackListenerId = optlyInstance.notificationCenter.addNotificationListener(
21332133
enums.NOTIFICATION_TYPES.TRACK,
@@ -2136,13 +2136,13 @@ describe('lib/optimizely', function() {
21362136
optlyInstance.notificationCenter.removeNotificationListener(trackListenerId);
21372137
optlyInstance.activate('testExperiment', 'testUser');
21382138
optlyInstance.track('testEvent', 'testUser');
2139-
sinon.assert.calledOnce(decisionListener);
2139+
sinon.assert.calledOnce(activateListener);
21402140
});
21412141

21422142
it('should clear all notification listeners when clearAllNotificationListeners is called', function() {
21432143
optlyInstance.notificationCenter.addNotificationListener(
21442144
enums.NOTIFICATION_TYPES.ACTIVATE,
2145-
decisionListener
2145+
activateListener
21462146
);
21472147
optlyInstance.notificationCenter.addNotificationListener(
21482148
enums.NOTIFICATION_TYPES.TRACK,
@@ -2152,14 +2152,14 @@ describe('lib/optimizely', function() {
21522152
optlyInstance.activate('testExperiment', 'testUser');
21532153
optlyInstance.track('testEvent', 'testUser');
21542154

2155-
sinon.assert.notCalled(decisionListener);
2155+
sinon.assert.notCalled(activateListener);
21562156
sinon.assert.notCalled(trackListener);
21572157
});
21582158

21592159
it('should clear listeners of certain notification type when clearNotificationListeners is called', function() {
21602160
optlyInstance.notificationCenter.addNotificationListener(
21612161
enums.NOTIFICATION_TYPES.ACTIVATE,
2162-
decisionListener
2162+
activateListener
21632163
);
21642164
optlyInstance.notificationCenter.addNotificationListener(
21652165
enums.NOTIFICATION_TYPES.TRACK,
@@ -2169,47 +2169,47 @@ describe('lib/optimizely', function() {
21692169
optlyInstance.activate('testExperiment', 'testUser');
21702170
optlyInstance.track('testEvent', 'testUser');
21712171

2172-
sinon.assert.notCalled(decisionListener);
2172+
sinon.assert.notCalled(activateListener);
21732173
sinon.assert.calledOnce(trackListener);
21742174
});
21752175

21762176
it('should only call the listener once after the same listener was added twice', function() {
21772177
optlyInstance.notificationCenter.addNotificationListener(
21782178
enums.NOTIFICATION_TYPES.ACTIVATE,
2179-
decisionListener
2179+
activateListener
21802180
);
21812181
optlyInstance.notificationCenter.addNotificationListener(
21822182
enums.NOTIFICATION_TYPES.ACTIVATE,
2183-
decisionListener
2183+
activateListener
21842184
);
21852185
optlyInstance.activate('testExperiment', 'testUser');
2186-
sinon.assert.calledOnce(decisionListener);
2186+
sinon.assert.calledOnce(activateListener);
21872187
});
21882188

21892189
it('should not add a listener with an invalid type argument', function() {
21902190
var listenerId = optlyInstance.notificationCenter.addNotificationListener(
21912191
'not a notification type',
2192-
decisionListener
2192+
activateListener
21932193
);
21942194
assert.strictEqual(listenerId, -1);
21952195
optlyInstance.activate('testExperiment', 'testUser');
2196-
sinon.assert.notCalled(decisionListener);
2196+
sinon.assert.notCalled(activateListener);
21972197
optlyInstance.track('testEvent', 'testUser');
2198-
sinon.assert.notCalled(decisionListener);
2198+
sinon.assert.notCalled(activateListener);
21992199
});
22002200

22012201
it('should call multiple notification listeners for activate when activate is called', function() {
22022202
optlyInstance.notificationCenter.addNotificationListener(
22032203
enums.NOTIFICATION_TYPES.ACTIVATE,
2204-
decisionListener
2204+
activateListener
22052205
);
22062206
optlyInstance.notificationCenter.addNotificationListener(
22072207
enums.NOTIFICATION_TYPES.ACTIVATE,
2208-
decisionListener2
2208+
activateListener2
22092209
);
22102210
optlyInstance.activate('testExperiment', 'testUser');
2211-
sinon.assert.calledOnce(decisionListener);
2212-
sinon.assert.calledOnce(decisionListener2);
2211+
sinon.assert.calledOnce(activateListener);
2212+
sinon.assert.calledOnce(activateListener2);
22132213
});
22142214

22152215
it('should call multiple notification listeners for track when track is called', function() {
@@ -2230,7 +2230,7 @@ describe('lib/optimizely', function() {
22302230
it('should pass the correct arguments to an activate listener when activate is called', function() {
22312231
optlyInstance.notificationCenter.addNotificationListener(
22322232
enums.NOTIFICATION_TYPES.ACTIVATE,
2233-
decisionListener
2233+
activateListener
22342234
);
22352235
optlyInstance.activate('testExperiment', 'testUser');
22362236
var expectedImpressionEvent = {
@@ -2279,7 +2279,7 @@ describe('lib/optimizely', function() {
22792279
variation: instanceExperiments[0].variations[1],
22802280
logEvent: expectedImpressionEvent,
22812281
};
2282-
sinon.assert.calledWith(decisionListener, expectedArgument);
2282+
sinon.assert.calledWith(activateListener, expectedArgument);
22832283
});
22842284

22852285
it('should pass the correct arguments to an activate listener when activate is called with attributes', function() {
@@ -2288,7 +2288,7 @@ describe('lib/optimizely', function() {
22882288
};
22892289
optlyInstance.notificationCenter.addNotificationListener(
22902290
enums.NOTIFICATION_TYPES.ACTIVATE,
2291-
decisionListener
2291+
activateListener
22922292
);
22932293
optlyInstance.activate('testExperiment', 'testUser', attributes);
22942294
var expectedImpressionEvent = {
@@ -2344,7 +2344,7 @@ describe('lib/optimizely', function() {
23442344
variation: instanceExperiments[0].variations[1],
23452345
logEvent: expectedImpressionEvent,
23462346
};
2347-
sinon.assert.calledWith(decisionListener, expectedArgument);
2347+
sinon.assert.calledWith(activateListener, expectedArgument);
23482348
});
23492349

23502350
it('should pass the correct arguments to a track listener when track is called', function() {
@@ -2519,6 +2519,80 @@ describe('lib/optimizely', function() {
25192519
};
25202520
sinon.assert.calledWith(trackListener, expectedArgument);
25212521
});
2522+
2523+
describe('Decision Listener', function() {
2524+
var decisionListener;
2525+
beforeEach(function() {
2526+
decisionListener = sinon.spy();
2527+
optlyInstance.notificationCenter.addNotificationListener(
2528+
enums.NOTIFICATION_TYPES.DECISION,
2529+
decisionListener
2530+
);
2531+
});
2532+
2533+
describe('activate', function() {
2534+
it('should send notification with actual variation key when activate returns variation', function() {
2535+
bucketStub.returns('111129');
2536+
var variation = optlyInstance.activate('testExperiment', 'testUser');
2537+
assert.strictEqual(variation, 'variation');
2538+
sinon.assert.calledWith(decisionListener, {
2539+
type: DECISION_INFO_TYPES.EXPERIMENT,
2540+
userId: 'testUser',
2541+
attributes: {},
2542+
decisionInfo: {
2543+
experimentKey: 'testExperiment',
2544+
variationKey: variation
2545+
}
2546+
});
2547+
});
2548+
2549+
it('should send notification with null variation key when activate returns null', function() {
2550+
bucketStub.returns(null);
2551+
var variation = optlyInstance.activate('testExperiment', 'testUser');
2552+
assert.isNull(variation);
2553+
sinon.assert.calledWith(decisionListener, {
2554+
type: DECISION_INFO_TYPES.EXPERIMENT,
2555+
userId: 'testUser',
2556+
attributes: {},
2557+
decisionInfo: {
2558+
experimentKey: 'testExperiment',
2559+
variationKey: null
2560+
}
2561+
});
2562+
});
2563+
});
2564+
2565+
describe('getVariation', function() {
2566+
it('should send notification with actual variation key when getVariation returns variation', function() {
2567+
bucketStub.returns('111129');
2568+
var variation = optlyInstance.getVariation('testExperiment', 'testUser');
2569+
assert.strictEqual(variation, 'variation');
2570+
sinon.assert.calledWith(decisionListener, {
2571+
type: DECISION_INFO_TYPES.EXPERIMENT,
2572+
userId: 'testUser',
2573+
attributes: {},
2574+
decisionInfo: {
2575+
experimentKey: 'testExperiment',
2576+
variationKey: variation
2577+
}
2578+
});
2579+
});
2580+
2581+
it('should send notification with null variation key when getVariation returns null', function() {
2582+
var variation = optlyInstance.getVariation('testExperimentWithAudiences', 'testUser', {});
2583+
assert.isNull(variation);
2584+
sinon.assert.calledWith(decisionListener, {
2585+
type: DECISION_INFO_TYPES.EXPERIMENT,
2586+
userId: 'testUser',
2587+
attributes: {},
2588+
decisionInfo: {
2589+
experimentKey: 'testExperimentWithAudiences',
2590+
variationKey: null
2591+
}
2592+
});
2593+
});
2594+
});
2595+
});
25222596
});
25232597
});
25242598

packages/optimizely-sdk/lib/utils/enums/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,23 @@ exports.NODE_CLIENT_VERSION = '3.1.0-beta1';
173173
* - attributes {Object|undefined}
174174
* - eventTags {Object|undefined}
175175
* - logEvent {Object}
176+
*
177+
* DECISION: A decision is made in the system. i.e. user activation,
178+
* feature access or feature-variable value retrieval
179+
* Callbacks will receive an object argument with the following properties:
180+
* - type {string}
181+
* - userId {string}
182+
* - attributes {Object|undefined}
183+
* - decisionInfo {Object|undefined}
176184
*/
177185
exports.NOTIFICATION_TYPES = {
178186
ACTIVATE: 'ACTIVATE:experiment, user_id,attributes, variation, event',
179187
TRACK: 'TRACK:event_key, user_id, attributes, event_tags, event',
188+
DECISION: 'DECISION:type, userId, attributes, decisionInfo',
189+
};
190+
191+
exports.DECISION_INFO_TYPES = {
192+
EXPERIMENT: 'experiment',
180193
};
181194

182195
/*

0 commit comments

Comments
 (0)