Skip to content

Commit d658697

Browse files
authored
feat: Implement createUserContext for decide API (#632)
* Implement createUserContext * Clean up * Incorporate comments
1 parent dfc1b7f commit d658697

File tree

7 files changed

+726
-23
lines changed

7 files changed

+726
-23
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import Optimizely from '../../optimizely';
3030
import projectConfig from '../project_config';
3131
import AudienceEvaluator from '../audience_evaluator';
3232
import errorHandler from '../../plugins/error_handler';
33-
import eventBuilder from '../../core/event_builder/index.js';
3433
import eventDispatcher from '../../plugins/event_dispatcher/index.node';
3534
import * as jsonSchemaValidator from '../../utils/json_schema_validator';
3635
import {
@@ -951,7 +950,6 @@ describe('lib/core/decision_service', function() {
951950
jsonSchemaValidator: jsonSchemaValidator,
952951
isValidInstance: true,
953952
logger: createdLogger,
954-
eventBuilder: eventBuilder,
955953
eventDispatcher: eventDispatcher,
956954
errorHandler: errorHandler,
957955
});

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

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ import eventProcessor from '../core/event_processor';
2020
import * as logging from '@optimizely/js-sdk-logging';
2121

2222
import Optimizely from './';
23+
import OptimizelyUserContext from '../optimizely_user_context';
2324
import AudienceEvaluator from '../core/audience_evaluator';
2425
import bluebird from 'bluebird';
2526
import bucketer from '../core/bucketer';
2627
import * as projectConfigManager from '../core/project_config/project_config_manager';
2728
import * as enums from '../utils/enums';
28-
import * as eventBuilder from '../core/event_builder';
2929
import eventDispatcher from '../plugins/event_dispatcher/index.node';
3030
import errorHandler from '../plugins/error_handler';
3131
import fns from '../utils/fns';
@@ -210,7 +210,6 @@ describe('lib/optimizely', function() {
210210
it('should not log an error when sdkKey is provided and datafile is not provided', function() {
211211
new Optimizely({
212212
clientEngine: 'node-sdk',
213-
eventBuilder: eventBuilder,
214213
errorHandler: stubErrorHandler,
215214
eventDispatcher: eventDispatcher,
216215
isValidInstance: true,
@@ -2626,7 +2625,6 @@ describe('lib/optimizely', function() {
26262625
optlyInstance = new Optimizely({
26272626
clientEngine: 'node-sdk',
26282627
datafile: testData.getTestProjectConfig(),
2629-
eventBuilder: eventBuilder,
26302628
errorHandler: errorHandler,
26312629
eventDispatcher: eventDispatcher,
26322630
jsonSchemaValidator: jsonSchemaValidator,
@@ -2676,7 +2674,6 @@ describe('lib/optimizely', function() {
26762674
optlyInstance = new Optimizely({
26772675
clientEngine: 'node-sdk',
26782676
datafile: testData.getTestProjectConfig(),
2679-
eventBuilder: eventBuilder,
26802677
errorHandler: errorHandler,
26812678
eventDispatcher: eventDispatcher,
26822679
jsonSchemaValidator: jsonSchemaValidator,
@@ -2723,7 +2720,6 @@ describe('lib/optimizely', function() {
27232720
var optly = new Optimizely({
27242721
clientEngine: 'node-sdk',
27252722
datafile: testData.getTestProjectConfigWithFeatures(),
2726-
eventBuilder: eventBuilder,
27272723
errorHandler: errorHandler,
27282724
eventDispatcher: eventDispatcher,
27292725
jsonSchemaValidator: jsonSchemaValidator,
@@ -4313,6 +4309,95 @@ describe('lib/optimizely', function() {
43134309
});
43144310
});
43154311

4312+
describe('decide APIs', function() {
4313+
var optlyInstance;
4314+
var bucketStub;
4315+
var createdLogger = logger.createLogger({
4316+
logLevel: LOG_LEVEL.INFO,
4317+
logToConsole: false,
4318+
});
4319+
beforeEach(function() {
4320+
optlyInstance = new Optimizely({
4321+
clientEngine: 'node-sdk',
4322+
datafile: testData.getTestDecideProjectConfig(),
4323+
errorHandler: errorHandler,
4324+
eventDispatcher: eventDispatcher,
4325+
jsonSchemaValidator: jsonSchemaValidator,
4326+
logger: createdLogger,
4327+
isValidInstance: true,
4328+
eventBatchSize: 1,
4329+
});
4330+
4331+
bucketStub = sinon.stub(bucketer, 'bucket');
4332+
sinon.stub(errorHandler, 'handleError');
4333+
sinon.stub(createdLogger, 'log');
4334+
sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c');
4335+
});
4336+
4337+
afterEach(function() {
4338+
bucketer.bucket.restore();
4339+
errorHandler.handleError.restore();
4340+
createdLogger.log.restore();
4341+
fns.uuid.restore();
4342+
});
4343+
describe('#createUserContext', function() {
4344+
it('should create OptimizelyUserContext with provided attributes and userId', function() {
4345+
var userId = 'testUser1';
4346+
var attributes = { test_attribute: 'test_value' };
4347+
var user = optlyInstance.createUserContext(userId, attributes);
4348+
assert.instanceOf(user, OptimizelyUserContext);
4349+
assert.deepEqual(optlyInstance, user.getOptimizely());
4350+
assert.deepEqual(attributes, user.getAttributes());
4351+
assert.deepEqual(userId, user.getUserId());
4352+
});
4353+
4354+
it('should create OptimizelyUserContext when no attributes provided', function() {
4355+
var userId = 'testUser2';
4356+
var user = optlyInstance.createUserContext(userId);
4357+
assert.instanceOf(user, OptimizelyUserContext);
4358+
assert.deepEqual(optlyInstance, user.getOptimizely());
4359+
assert.deepEqual({}, user.getAttributes());
4360+
assert.deepEqual(userId, user.getUserId());
4361+
});
4362+
4363+
it('should create multiple instances of OptimizelyUserContext', function() {
4364+
var userId1 = 'testUser1'
4365+
var userId2 = 'testUser2';
4366+
var attributes1 = { test_attribute: 'test_value' };
4367+
var user1 = optlyInstance.createUserContext(userId1, attributes1);
4368+
var user2 = optlyInstance.createUserContext(userId2);
4369+
assert.instanceOf(user1, OptimizelyUserContext);
4370+
assert.deepEqual(user1.getOptimizely(), optlyInstance);
4371+
assert.deepEqual(user1.getAttributes(), attributes1);
4372+
assert.deepEqual(user1.getUserId(), userId1);
4373+
assert.instanceOf(user2, OptimizelyUserContext);
4374+
assert.deepEqual(user2.getOptimizely(), optlyInstance);
4375+
assert.deepEqual(user2.getAttributes(), {});
4376+
assert.deepEqual(user2.getUserId(), userId2);
4377+
});
4378+
4379+
it('should call the error handler for invalid user ID and return null', function() {
4380+
assert.isNull(optlyInstance.createUserContext(null));
4381+
sinon.assert.calledOnce(errorHandler.handleError);
4382+
var errorMessage = errorHandler.handleError.lastCall.args[0].message;
4383+
assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id'));
4384+
sinon.assert.calledOnce(createdLogger.log);
4385+
var logMessage = createdLogger.log.args[0][1];
4386+
assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id'));
4387+
});
4388+
4389+
it('should call the error handler for invalid attributes and return null', function() {
4390+
assert.isNull(optlyInstance.createUserContext('user1', 'invalid_attributes'));
4391+
sinon.assert.calledOnce(errorHandler.handleError);
4392+
var errorMessage = errorHandler.handleError.lastCall.args[0].message;
4393+
assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR'));
4394+
sinon.assert.calledOnce(createdLogger.log);
4395+
var logMessage = createdLogger.log.args[0][1];
4396+
assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR'));
4397+
});
4398+
});
4399+
});
4400+
43164401
//tests separated out from APIs because of mock bucketing
43174402
describe('getVariationBucketingIdAttribute', function() {
43184403
var optlyInstance;
@@ -4324,7 +4409,6 @@ describe('lib/optimizely', function() {
43244409
optlyInstance = new Optimizely({
43254410
clientEngine: 'node-sdk',
43264411
datafile: testData.getTestProjectConfig(),
4327-
eventBuilder: eventBuilder,
43284412
errorHandler: errorHandler,
43294413
eventDispatcher: eventDispatcher,
43304414
jsonSchemaValidator: jsonSchemaValidator,
@@ -4379,7 +4463,6 @@ describe('lib/optimizely', function() {
43794463
optlyInstance = new Optimizely({
43804464
clientEngine: 'node-sdk',
43814465
datafile: testData.getTestProjectConfigWithFeatures(),
4382-
eventBuilder: eventBuilder,
43834466
errorHandler: errorHandler,
43844467
eventDispatcher: eventDispatcher,
43854468
jsonSchemaValidator: jsonSchemaValidator,
@@ -4412,7 +4495,6 @@ describe('lib/optimizely', function() {
44124495
lasers: 300,
44134496
message: 'this is not a valid datafile',
44144497
},
4415-
eventBuilder: eventBuilder,
44164498
errorHandler: errorHandler,
44174499
eventDispatcher: eventDispatcher,
44184500
jsonSchemaValidator: jsonSchemaValidator,
@@ -4936,7 +5018,6 @@ describe('lib/optimizely', function() {
49365018
lasers: 300,
49375019
message: 'this is not a valid datafile',
49385020
},
4939-
eventBuilder: eventBuilder,
49405021
errorHandler: errorHandler,
49415022
eventDispatcher: eventDispatcher,
49425023
jsonSchemaValidator: jsonSchemaValidator,
@@ -4976,7 +5057,6 @@ describe('lib/optimizely', function() {
49765057
optlyInstance = new Optimizely({
49775058
clientEngine: 'node-sdk',
49785059
datafile: testData.getTestProjectConfigWithFeatures(),
4979-
eventBuilder: eventBuilder,
49805060
errorHandler: errorHandler,
49815061
eventDispatcher: eventDispatcher,
49825062
jsonSchemaValidator: jsonSchemaValidator,
@@ -7147,7 +7227,6 @@ describe('lib/optimizely', function() {
71477227
optlyInstance = new Optimizely({
71487228
clientEngine: 'node-sdk',
71497229
datafile: testData.getTypedAudiencesConfig(),
7150-
eventBuilder: eventBuilder,
71517230
errorHandler: errorHandler,
71527231
eventDispatcher: eventDispatcher,
71537232
jsonSchemaValidator: jsonSchemaValidator,
@@ -7279,7 +7358,6 @@ describe('lib/optimizely', function() {
72797358
optlyInstance = new Optimizely({
72807359
clientEngine: 'node-sdk',
72817360
datafile: testData.getTypedAudiencesConfig(),
7282-
eventBuilder: eventBuilder,
72837361
errorHandler: errorHandler,
72847362
eventDispatcher: eventDispatcher,
72857363
jsonSchemaValidator: jsonSchemaValidator,
@@ -7470,7 +7548,6 @@ describe('lib/optimizely', function() {
74707548
optlyInstance = new Optimizely({
74717549
clientEngine: 'node-sdk',
74727550
datafile: testData.getTestProjectConfig(),
7473-
eventBuilder: eventBuilder,
74747551
errorHandler: errorHandler,
74757552
eventDispatcher: eventDispatcher,
74767553
jsonSchemaValidator: jsonSchemaValidator,
@@ -7758,7 +7835,6 @@ describe('lib/optimizely', function() {
77587835
optlyInstance = new Optimizely({
77597836
clientEngine: 'node-sdk',
77607837
datafile: testData.getTestProjectConfig(),
7761-
eventBuilder: eventBuilder,
77627838
errorHandler: errorHandler,
77637839
eventDispatcher: eventDispatcher,
77647840
jsonSchemaValidator: jsonSchemaValidator,
@@ -7789,7 +7865,6 @@ describe('lib/optimizely', function() {
77897865
optlyInstance = new Optimizely({
77907866
clientEngine: 'node-sdk',
77917867
datafile: testData.getTestProjectConfig(),
7792-
eventBuilder: eventBuilder,
77937868
errorHandler: errorHandler,
77947869
eventDispatcher: eventDispatcher,
77957870
jsonSchemaValidator: jsonSchemaValidator,
@@ -8169,7 +8244,6 @@ describe('lib/optimizely', function() {
81698244
optlyInstance = new Optimizely({
81708245
clientEngine: 'node-sdk',
81718246
datafile: testData.getTestProjectConfig(),
8172-
eventBuilder: eventBuilder,
81738247
errorHandler: {
81748248
handleError: function() {},
81758249
},

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
FeatureVariable,
2828
OptimizelyOptions
2929
} from '../shared_types';
30+
import OptimizelyUserContext from '../optimizely_user_context';
3031
import { createProjectConfigManager, ProjectConfigManager } from '../core/project_config/project_config_manager';
3132
import { createNotificationCenter, NotificationCenter } from '../core/notification_center';
3233
import { createDecisionService, DecisionService, DecisionObj } from '../core/decision_service';
@@ -55,11 +56,10 @@ const MODULE_NAME = 'OPTIMIZELY';
5556

5657
const DEFAULT_ONREADY_TIMEOUT = 30000;
5758

58-
5959
// TODO: Make feature_key, user_id, variable_key, experiment_key, event_key camelCase
60-
export type InputKey = 'feature_key' | 'user_id' | 'variable_key' | 'experiment_key' | 'event_key' | 'variation_id';
60+
type InputKey = 'feature_key' | 'user_id' | 'variable_key' | 'experiment_key' | 'event_key' | 'variation_id';
6161

62-
export type StringInputs = Partial<Record<InputKey, unknown>>;
62+
type StringInputs = Partial<Record<InputKey, unknown>>;
6363

6464
/**
6565
* The Optimizely class
@@ -1415,4 +1415,29 @@ export default class Optimizely {
14151415

14161416
return Promise.race([this.readyPromise, timeoutPromise]);
14171417
}
1418+
1419+
//============ decide ============//
1420+
1421+
/**
1422+
* Creates a context of the user for which decision APIs will be called.
1423+
*
1424+
* A user context will be created successfully even when the SDK is not fully configured yet, so no
1425+
* this.isValidInstance() check is performed here.
1426+
*
1427+
* @param {string} userId The user ID to be used for bucketing.
1428+
* @param {UserAttributes} attributes Optional user attributes.
1429+
* @return {OptimizelyUserContext|null} An OptimizelyUserContext associated with this OptimizelyClient or
1430+
* null if provided inputs are invalid
1431+
*/
1432+
createUserContext(userId: string, attributes?: UserAttributes): OptimizelyUserContext | null {
1433+
if (!this.validateInputs({ user_id: userId }, attributes)) {
1434+
return null;
1435+
}
1436+
1437+
return new OptimizelyUserContext({
1438+
optimizely: this,
1439+
userId,
1440+
attributes
1441+
});
1442+
}
14181443
}

0 commit comments

Comments
 (0)