From 97204df8bf621f8006ce527602b06e53553aa5c1 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Wed, 9 Dec 2020 16:55:26 -0800 Subject: [PATCH 01/11] Implement decideForKeys --- .../optimizely-sdk/lib/optimizely/index.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index e743416cd..4b4a31d45 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -1593,4 +1593,31 @@ export default class Optimizely { return allDecideOptions; } + + decideForKeys( + user: OptimizelyUserContext, + keys: string[], + options: OptimizelyDecideOptions[] = [] + ): { [key: string]: OptimizelyDecision } { + const configObj = this.projectConfigManager.getConfig(); + const decisionMap: { [key: string]: OptimizelyDecision } = {}; + if (!this.isValidInstance() || !configObj) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideForKeys')); + return decisionMap; + } + if (keys.length === 0) { + return decisionMap; + } + + const allDecideOptions = this.getAllDecideOptions(options); + let decision: OptimizelyDecision; + keys.forEach(key => { + decision = this.decide(user, key, options); + if (!allDecideOptions[OptimizelyDecideOptions.ENABLED_FLAGS_ONLY] || decision.enabled) { + decisionMap[key] = decision; + } + }); + + return decisionMap; + } } From 22ae9542a6fd48fa835d7b7c7a85a58988848fb3 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Thu, 10 Dec 2020 09:31:41 -0800 Subject: [PATCH 02/11] Implememt decideAll and add comments --- .../optimizely-sdk/lib/optimizely/index.ts | 40 +++++++++++++++++-- .../lib/optimizely_user_context/index.ts | 32 +++++++++++---- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 4b4a31d45..683c12477 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -1594,6 +1594,16 @@ export default class Optimizely { return allDecideOptions; } + /** + * Returns a key-map of decision results for multiple flag keys and a user context. + * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. + * The SDK will always return key-mapped decisions. When it cannot process requests, it will return an empty map after logging the errors. + * @param {OptimizelyUserContext} user A user context associated with this OptimizelyClient + * @param {string[]} keys An array of flag keys for which decisions will be made. + * @param {OptimizelyDecideOptions[]} options An array of options for decision-making. + * @return {[key: string]: OptimizelyDecision} All decision results mapped by flag keys. + */ + decideForKeys( user: OptimizelyUserContext, keys: string[], @@ -1610,14 +1620,36 @@ export default class Optimizely { } const allDecideOptions = this.getAllDecideOptions(options); - let decision: OptimizelyDecision; + let optimizelyDecision: OptimizelyDecision; keys.forEach(key => { - decision = this.decide(user, key, options); - if (!allDecideOptions[OptimizelyDecideOptions.ENABLED_FLAGS_ONLY] || decision.enabled) { - decisionMap[key] = decision; + optimizelyDecision = this.decide(user, key, options); + if (!allDecideOptions[OptimizelyDecideOptions.ENABLED_FLAGS_ONLY] || optimizelyDecision.enabled) { + decisionMap[key] = optimizelyDecision; } }); return decisionMap; } + + /** + * Returns a key-map of decision results for all active flag keys. + * @param {OptimizelyUserContext} user A user context associated with this OptimizelyClient + * @param {OptimizelyDecideOptions[]} options An array of options for decision-making. + * @return {[key: string]: OptimizelyDecision} All decision results mapped by flag keys. + */ + decideAll( + user: OptimizelyUserContext, + options: OptimizelyDecideOptions[] = [] + ): { [key: string]: OptimizelyDecision } { + const configObj = this.projectConfigManager.getConfig(); + const decisionMap: { [key: string]: OptimizelyDecision } = {}; + if (!this.isValidInstance() || !configObj) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideForKeys')); + return decisionMap; + } + + const allFlagKeys = Object.keys(configObj.featureKeyMap); + + return this.decideForKeys(user, allFlagKeys, options); + } } diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts index cde19959d..09a3dc838 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts @@ -61,7 +61,7 @@ export default class OptimizelyUserContext { * Returns a decision result for a given flag key and a user context, which contains all data required to deliver the flag. * If the SDK finds an error, it will return a decision with null for variationKey. The decision will include an error message in reasons. * @param {string} key A flag key for which a decision will be made. - * @param {OptimizelyDecideOption} options A list of options for decision-making. + * @param {OptimizelyDecideOption} options An array of options for decision-making. * @return {OptimizelyDecision} A decision result. */ decide( @@ -72,13 +72,31 @@ export default class OptimizelyUserContext { return this.optimizely.decide(this, key, options); } - decideForKeys(): void { - //TODO: implement - return; + /** + * Returns a key-map of decision results for multiple flag keys and a user context. + * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. + * The SDK will always return key-mapped decisions. When it cannot process requests, it will return an empty map after logging the errors. + * @param {string[]} keys An array of flag keys for which decisions will be made. + * @param {OptimizelyDecideOptions[]} options An array of options for decision-making. + * @return {[key: string]: OptimizelyDecision} All decision results mapped by flag keys. + */ + decideForKeys( + keys: string[], + options: OptimizelyDecideOptions[] = [], + ): { [key: string]: OptimizelyDecision } { + + return this.optimizely.decideForKeys(this, keys, options); } - decideAll(): void { - //TODO: implement - return; + /** + * Returns a key-map of decision results for all active flag keys. + * @param {OptimizelyDecideOptions[]} options An array of options for decision-making. + * @return {[key: string]: OptimizelyDecision} All decision results mapped by flag keys. + */ + decideAll( + options: OptimizelyDecideOptions[] = [] + ): { [key: string]: OptimizelyDecision } { + + return this.optimizely.decideForKeys(this, options); } } From cc0a21f57616329d2a970ce8a2c9498eddd4267b Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Thu, 10 Dec 2020 13:12:27 -0800 Subject: [PATCH 03/11] Add decideAll and decideForKeys unit tests to optimizely --- .../lib/optimizely/index.tests.js | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 89254c184..b6c9493bc 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -4333,6 +4333,7 @@ describe('lib/optimizely', function() { logLevel: LOG_LEVEL.INFO, logToConsole: false, }); + describe('#createUserContext', function() { beforeEach(function() { optlyInstance = new Optimizely({ @@ -4965,6 +4966,241 @@ describe('lib/optimizely', function() { }); }); }); + + describe('#decideForKeys', function() { + var userId = 'tester'; + beforeEach(function() { + optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + datafile: testData.getTestDecideProjectConfig(), + errorHandler: errorHandler, + eventDispatcher: eventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: [], + }); + + sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); + }); + + afterEach(function() { + optlyInstance.notificationCenter.sendNotifications.restore(); + }); + + it('should return decision results map with single flag key provided for feature_test and dispatch an event', function() { + var flagKey = 'feature_2'; + var user = optlyInstance.createUserContext(userId); + var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); + var decisionsMap = optlyInstance.decideForKeys(user, [ flagKey ]); + var decision = decisionsMap[flagKey]; + var expectedDecision = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: expectedVariables, + ruleKey: 'exp_no_audience', + flagKey: flagKey, + userContext: user, + reasons: [], + } + assert.deepEqual(Object.values(decisionsMap).length, 1); + assert.deepEqual(decision, expectedDecision); + sinon.assert.calledOnce(optlyInstance.eventDispatcher.dispatchEvent); + sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4) + var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; + var decisionEventDispatched = notificationCallArgs[1].decisionInfo.decisionEventDispatched; + assert.deepEqual(decisionEventDispatched, true); + }); + + it('should return decision results map with two flag keys provided and dispatch events', function() { + var flagKeysArray = ['feature_1', 'feature_2']; + var user = optlyInstance.createUserContext(userId); + var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKeysArray[0], userId); + var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKeysArray[1], userId); + var decisionsMap = optlyInstance.decideForKeys(user, flagKeysArray); + var decision1 = decisionsMap[flagKeysArray[0]]; + var decision2 = decisionsMap[flagKeysArray[1]]; + var expectedDecision1 = { + variationKey: '18257766532', + enabled: true, + variables: expectedVariables1, + ruleKey: '18322080788', + flagKey: flagKeysArray[0], + userContext: user, + reasons: [], + } + var expectedDecision2 = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: expectedVariables2, + ruleKey: 'exp_no_audience', + flagKey: flagKeysArray[1], + userContext: user, + reasons: [], + } + assert.deepEqual(Object.values(decisionsMap).length, 2); + assert.deepEqual(decision1, expectedDecision1); + assert.deepEqual(decision2, expectedDecision2); + sinon.assert.calledTwice(optlyInstance.eventDispatcher.dispatchEvent); + }); + }); + + describe('#decideAll', function() { + var userId = 'tester'; + describe('with empty default decide options', function() { + beforeEach(function() { + optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + datafile: testData.getTestDecideProjectConfig(), + errorHandler: errorHandler, + eventDispatcher: eventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: [], + }); + + sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); + }); + + afterEach(function() { + optlyInstance.notificationCenter.sendNotifications.restore(); + }); + + it('should return decision results map with all flag keys provided and dispatch events', function() { + var configObj = optlyInstance.projectConfigManager.getConfig(); + var allFlagKeysArray = Object.keys(configObj.featureKeyMap); + var user = optlyInstance.createUserContext(userId); + var expectedVariables1 = optlyInstance.getAllFeatureVariables(allFlagKeysArray[0], userId); + var expectedVariables2 = optlyInstance.getAllFeatureVariables(allFlagKeysArray[1], userId); + var expectedVariables3 = optlyInstance.getAllFeatureVariables(allFlagKeysArray[2], userId); + var decisionsMap = user.decideAll(allFlagKeysArray); + var decision1 = decisionsMap[allFlagKeysArray[0]]; + var decision2 = decisionsMap[allFlagKeysArray[1]]; + var decision3 = decisionsMap[allFlagKeysArray[2]]; + var expectedDecision1 = { + variationKey: '18257766532', + enabled: true, + variables: expectedVariables1, + ruleKey: '18322080788', + flagKey: allFlagKeysArray[0], + userContext: user, + reasons: [], + } + var expectedDecision2 = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: expectedVariables2, + ruleKey: 'exp_no_audience', + flagKey: allFlagKeysArray[1], + userContext: user, + reasons: [], + } + var expectedDecision3 = { + variationKey: '', + enabled: false, + variables: expectedVariables3, + ruleKey: '', + flagKey: allFlagKeysArray[2], + userContext: user, + reasons: [], + } + assert.deepEqual(Object.values(decisionsMap).length, allFlagKeysArray.length); + assert.deepEqual(decision1, expectedDecision1); + assert.deepEqual(decision2, expectedDecision2); + assert.deepEqual(decision3, expectedDecision3); + sinon.assert.calledThrice(optlyInstance.eventDispatcher.dispatchEvent); + }); + }); + + describe('with ENABLED_FLAGS_ONLY flag in default decide options', function() { + beforeEach(function() { + optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + datafile: testData.getTestDecideProjectConfig(), + errorHandler: errorHandler, + eventDispatcher: eventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: [ OptimizelyDecideOptions.ENABLED_FLAGS_ONLY ], + }); + + sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); + }); + + afterEach(function() { + optlyInstance.notificationCenter.sendNotifications.restore(); + }); + + it('should return decision results map with only enabled flags and dispatch events', function() { + var flagKey1 = 'feature_1'; + var flagKey2 = 'feature_2'; + var user = optlyInstance.createUserContext(userId, {"gender": "female"}); + var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKey1, userId); + var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKey2, userId); + var decisionsMap = optlyInstance.decideAll(user); + var decision1 = decisionsMap[flagKey1]; + var decision2 = decisionsMap[flagKey2]; + var expectedDecision1 = { + variationKey: '18257766532', + enabled: true, + variables: expectedVariables1, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: user, + reasons: [], + } + var expectedDecision2 = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: expectedVariables2, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: user, + reasons: [], + } + assert.deepEqual(Object.values(decisionsMap).length, 2); + assert.deepEqual(decision1, expectedDecision1); + assert.deepEqual(decision2, expectedDecision2); + sinon.assert.calledThrice(optlyInstance.eventDispatcher.dispatchEvent); + }); + + it('should return decision results map with only enabled flags and excluded variables when EXCLUDE_VARIABLES_FLAG is passed in', function() { + var flagKey1 = 'feature_1'; + var flagKey2 = 'feature_2'; + var user = optlyInstance.createUserContext(userId, {"gender": "female"}); + var decisionsMap = optlyInstance.decideAll(user, [ OptimizelyDecideOptions.EXCLUDE_VARIABLES ]); + var decision1 = decisionsMap[flagKey1]; + var decision2 = decisionsMap[flagKey2]; + var expectedDecision1 = { + variationKey: '18257766532', + enabled: true, + variables: {}, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: user, + reasons: [], + } + var expectedDecision2 = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: user, + reasons: [], + } + assert.deepEqual(Object.values(decisionsMap).length, 2); + assert.deepEqual(decision1, expectedDecision1); + assert.deepEqual(decision2, expectedDecision2); + sinon.assert.calledThrice(optlyInstance.eventDispatcher.dispatchEvent); + }); + }); + }); }); //tests separated out from APIs because of mock bucketing From 92e9b2d663803d7517e8c67ea7bac647d90f3c31 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Thu, 10 Dec 2020 14:22:42 -0800 Subject: [PATCH 04/11] Add unit tests for user context decideForKey and decideAll --- .../optimizely_user_context/index.tests.js | 93 ++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js index c5e8de72b..acc74a3c6 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2020, Optimizely, Inc. and contributors * + * Copyright 2020, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -20,6 +20,7 @@ import OptimizelyUserContext from './'; describe('lib/optimizely_user_context', function() { describe('APIs', function() { var fakeOptimizely; + var userId = 'tester'; describe('#setAttribute', function() { fakeOptimizely = { decide: sinon.stub().returns({}) @@ -134,7 +135,6 @@ describe('lib/optimizely_user_context', function() { }); describe('#decide', function() { - var userId = 'tester'; it('should return an expected decision object', function() { var flagKey = 'feature_1'; var fakeDecision = { @@ -157,5 +157,94 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(decision, fakeDecision); }); }); + + describe('##decideForKeys', function() { + it('should return an expected decision results map', function() { + var flagKey1 = 'feature_1'; + var flagKey2 = 'feature_2'; + var fakeDecisionMap = { + flagKey1: + { + variationKey: '18257766532', + enabled: true, + variables: {}, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: user, + reasons: [], + }, + flagKey2: + { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: user, + reasons: [], + }, + }; + fakeOptimizely = { + decideForKeys: sinon.stub().returns(fakeDecisionMap) + }; + var user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + var decisionMap = user.decideForKeys([ flagKey1, flagKey2 ]); + assert.deepEqual(decisionMap, fakeDecisionMap); + }); + }); + + describe('##decideAll', function() { + it('should return an expected decision results map', function() { + var flagKey1 = 'feature_1'; + var flagKey2 = 'feature_2'; + var flagKey3 = 'feature_3'; + var fakeDecisionMap = { + flagKey1: + { + variationKey: '18257766532', + enabled: true, + variables: {}, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: user, + reasons: [], + }, + flagKey2: + { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: user, + reasons: [], + }, + flagKey3: + { + variationKey: '', + enabled: false, + variables: {}, + ruleKey: '', + flagKey: flagKey3, + userContext: user, + reasons: [], + }, + }; + var fakeDecideForKeys = sinon.stub().returns(fakeDecisionMap); + fakeOptimizely = { + decideAll: sinon.stub().call(fakeDecideForKeys), + decideForKeys: fakeDecideForKeys + }; + var user = new OptimizelyUserContext({ + optimizely: fakeOptimizely, + userId, + }); + var decisionMap = user.decideAll(); + assert.deepEqual(decisionMap, fakeDecisionMap); + }); + }); }); }); From 4f66e46276dfc58a2dece3b94f2fe4e6264d3506 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Thu, 10 Dec 2020 14:42:15 -0800 Subject: [PATCH 05/11] Clean up --- packages/optimizely-sdk/lib/optimizely/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 683c12477..da1169b7b 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -1609,9 +1609,8 @@ export default class Optimizely { keys: string[], options: OptimizelyDecideOptions[] = [] ): { [key: string]: OptimizelyDecision } { - const configObj = this.projectConfigManager.getConfig(); const decisionMap: { [key: string]: OptimizelyDecision } = {}; - if (!this.isValidInstance() || !configObj) { + if (!this.isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideForKeys')); return decisionMap; } From 2767d0277ee204609d3026973a11a9b1ce938829 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Thu, 10 Dec 2020 15:07:13 -0800 Subject: [PATCH 06/11] Fix decideAll --- packages/optimizely-sdk/lib/optimizely/index.ts | 2 +- .../optimizely-sdk/lib/optimizely_user_context/index.tests.js | 4 +--- packages/optimizely-sdk/lib/optimizely_user_context/index.ts | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index da1169b7b..9d57c695f 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -1643,7 +1643,7 @@ export default class Optimizely { const configObj = this.projectConfigManager.getConfig(); const decisionMap: { [key: string]: OptimizelyDecision } = {}; if (!this.isValidInstance() || !configObj) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideForKeys')); + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideAll')); return decisionMap; } diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js index acc74a3c6..8605b6c8c 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js @@ -233,10 +233,8 @@ describe('lib/optimizely_user_context', function() { reasons: [], }, }; - var fakeDecideForKeys = sinon.stub().returns(fakeDecisionMap); fakeOptimizely = { - decideAll: sinon.stub().call(fakeDecideForKeys), - decideForKeys: fakeDecideForKeys + decideAll: sinon.stub().returns(fakeDecisionMap) }; var user = new OptimizelyUserContext({ optimizely: fakeOptimizely, diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts index 09a3dc838..a546ad7c0 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts @@ -97,6 +97,6 @@ export default class OptimizelyUserContext { options: OptimizelyDecideOptions[] = [] ): { [key: string]: OptimizelyDecision } { - return this.optimizely.decideForKeys(this, options); + return this.optimizely.decideAll(this, options); } } From 23da8c94e3ab8064b3760b29352d28b00df6d433 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 11 Dec 2020 10:36:00 -0800 Subject: [PATCH 07/11] Add more ENABLED_FLAGS_ONLY tests --- .../lib/optimizely/index.tests.js | 60 +++++++++++++++++-- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index b6c9493bc..54951f7ee 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -4912,16 +4912,10 @@ describe('lib/optimizely', function() { }); sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); - sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); - sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); }); afterEach(function() { optlyInstance.notificationCenter.sendNotifications.restore(); - errorHandler.handleError.restore(); - createdLogger.log.restore(); - fns.uuid.restore(); }); it('should make a decision and do not dispatch an event', function() { @@ -5044,6 +5038,27 @@ describe('lib/optimizely', function() { assert.deepEqual(decision2, expectedDecision2); sinon.assert.calledTwice(optlyInstance.eventDispatcher.dispatchEvent); }); + + it('should return decision results map with only enabled flags when ENABLED_FLAGS_ONLY flag is passed in and dispatch events', function() { + var flagKey1 = 'feature_2'; + var flagKey2 = 'feature_3'; + var user = optlyInstance.createUserContext(userId, {"gender": "female"}); + var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey1, userId); + var decisionsMap = optlyInstance.decideForKeys(user, [ flagKey1, flagKey2 ], [ OptimizelyDecideOptions.ENABLED_FLAGS_ONLY ]); + var decision = decisionsMap[flagKey1]; + var expectedDecision = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: expectedVariables, + ruleKey: 'exp_no_audience', + flagKey: flagKey1, + userContext: user, + reasons: [], + } + assert.deepEqual(Object.values(decisionsMap).length, 1); + assert.deepEqual(decision, expectedDecision); + sinon.assert.calledTwice(optlyInstance.eventDispatcher.dispatchEvent); + }); }); describe('#decideAll', function() { @@ -5113,6 +5128,39 @@ describe('lib/optimizely', function() { assert.deepEqual(decision3, expectedDecision3); sinon.assert.calledThrice(optlyInstance.eventDispatcher.dispatchEvent); }); + + it('should return decision results map with only enabled flags when ENABLED_FLAGS_ONLY flag is passed in and dispatch events', function() { + var flagKey1 = 'feature_1'; + var flagKey2 = 'feature_2'; + var user = optlyInstance.createUserContext(userId, {"gender": "female"}); + var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKey1, userId); + var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKey2, userId); + var decisionsMap = optlyInstance.decideAll(user, [ OptimizelyDecideOptions.ENABLED_FLAGS_ONLY ]); + var decision1 = decisionsMap[flagKey1]; + var decision2 = decisionsMap[flagKey2]; + var expectedDecision1 = { + variationKey: '18257766532', + enabled: true, + variables: expectedVariables1, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: user, + reasons: [], + } + var expectedDecision2 = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: expectedVariables2, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: user, + reasons: [], + } + assert.deepEqual(Object.values(decisionsMap).length, 2); + assert.deepEqual(decision1, expectedDecision1); + assert.deepEqual(decision2, expectedDecision2); + sinon.assert.calledThrice(optlyInstance.eventDispatcher.dispatchEvent); + }); }); describe('with ENABLED_FLAGS_ONLY flag in default decide options', function() { From 78480ec7e75da02cb4678a6a68a20c9deaa0f013 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 11 Dec 2020 11:05:10 -0800 Subject: [PATCH 08/11] update test params --- .../lib/optimizely_user_context/index.tests.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js index 8605b6c8c..2a5c56dd9 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js @@ -143,7 +143,7 @@ describe('lib/optimizely_user_context', function() { variables: {}, ruleKey: 'exp_no_audience', flagKey: flagKey, - userContext: user, + userContext: 'fakeUserContext', reasons: [], }; fakeOptimizely = { @@ -170,7 +170,7 @@ describe('lib/optimizely_user_context', function() { variables: {}, ruleKey: '18322080788', flagKey: flagKey1, - userContext: user, + userContext: 'fakeUserContext', reasons: [], }, flagKey2: @@ -180,7 +180,7 @@ describe('lib/optimizely_user_context', function() { variables: {}, ruleKey: 'exp_no_audience', flagKey: flagKey2, - userContext: user, + userContext: 'fakeUserContext', reasons: [], }, }; @@ -209,7 +209,7 @@ describe('lib/optimizely_user_context', function() { variables: {}, ruleKey: '18322080788', flagKey: flagKey1, - userContext: user, + userContext: 'fakeUserContext', reasons: [], }, flagKey2: @@ -219,7 +219,7 @@ describe('lib/optimizely_user_context', function() { variables: {}, ruleKey: 'exp_no_audience', flagKey: flagKey2, - userContext: user, + userContext: 'fakeUserContext', reasons: [], }, flagKey3: From 5d91f240f610cfd88667b070686aee3393b1d014 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 11 Dec 2020 14:24:24 -0800 Subject: [PATCH 09/11] Update comments --- packages/optimizely-sdk/lib/optimizely/index.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 9d57c695f..5c4c504f9 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -1595,13 +1595,13 @@ export default class Optimizely { } /** - * Returns a key-map of decision results for multiple flag keys and a user context. + * Returns an object of decision results for multiple flag keys and a user context. * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. - * The SDK will always return key-mapped decisions. When it cannot process requests, it will return an empty map after logging the errors. + * The SDK will always return an object of decisions. When it cannot process requests, it will return an empty object after logging the errors. * @param {OptimizelyUserContext} user A user context associated with this OptimizelyClient * @param {string[]} keys An array of flag keys for which decisions will be made. * @param {OptimizelyDecideOptions[]} options An array of options for decision-making. - * @return {[key: string]: OptimizelyDecision} All decision results mapped by flag keys. + * @return {[key: string]: OptimizelyDecision} An object of decision results mapped by flag keys. */ decideForKeys( @@ -1619,9 +1619,8 @@ export default class Optimizely { } const allDecideOptions = this.getAllDecideOptions(options); - let optimizelyDecision: OptimizelyDecision; keys.forEach(key => { - optimizelyDecision = this.decide(user, key, options); + const optimizelyDecision: OptimizelyDecision = this.decide(user, key, options); if (!allDecideOptions[OptimizelyDecideOptions.ENABLED_FLAGS_ONLY] || optimizelyDecision.enabled) { decisionMap[key] = optimizelyDecision; } @@ -1631,10 +1630,10 @@ export default class Optimizely { } /** - * Returns a key-map of decision results for all active flag keys. + * Returns an object of decision results for all active flag keys. * @param {OptimizelyUserContext} user A user context associated with this OptimizelyClient * @param {OptimizelyDecideOptions[]} options An array of options for decision-making. - * @return {[key: string]: OptimizelyDecision} All decision results mapped by flag keys. + * @return {[key: string]: OptimizelyDecision} An object of all decision results mapped by flag keys. */ decideAll( user: OptimizelyUserContext, From b9fab5e0e780627b137425b86f07948944207648 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 11 Dec 2020 14:56:02 -0800 Subject: [PATCH 10/11] Incorporate comments --- .../optimizely_user_context/index.tests.js | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js index 2a5c56dd9..7e62836a9 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js @@ -21,6 +21,7 @@ describe('lib/optimizely_user_context', function() { describe('APIs', function() { var fakeOptimizely; var userId = 'tester'; + var options = 'fakeOption'; describe('#setAttribute', function() { fakeOptimizely = { decide: sinon.stub().returns({}) @@ -153,13 +154,19 @@ describe('lib/optimizely_user_context', function() { optimizely: fakeOptimizely, userId, }); - var decision = user.decide(flagKey); + var decision = user.decide(flagKey, options); + sinon.assert.calledWithExactly( + fakeOptimizely.decide, + user, + flagKey, + options + ); assert.deepEqual(decision, fakeDecision); }); }); - describe('##decideForKeys', function() { - it('should return an expected decision results map', function() { + describe('#decideForKeys', function() { + it('should return an expected decision results object', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; var fakeDecisionMap = { @@ -191,13 +198,19 @@ describe('lib/optimizely_user_context', function() { optimizely: fakeOptimizely, userId, }); - var decisionMap = user.decideForKeys([ flagKey1, flagKey2 ]); + var decisionMap = user.decideForKeys([ flagKey1, flagKey2 ], options); + sinon.assert.calledWithExactly( + fakeOptimizely.decideForKeys, + user, + [ flagKey1, flagKey2 ], + options + ); assert.deepEqual(decisionMap, fakeDecisionMap); }); }); - describe('##decideAll', function() { - it('should return an expected decision results map', function() { + describe('#decideAll', function() { + it('should return an expected decision results object', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; var flagKey3 = 'feature_3'; @@ -240,7 +253,12 @@ describe('lib/optimizely_user_context', function() { optimizely: fakeOptimizely, userId, }); - var decisionMap = user.decideAll(); + var decisionMap = user.decideAll(options); + sinon.assert.calledWithExactly( + fakeOptimizely.decideAll, + user, + options + ); assert.deepEqual(decisionMap, fakeDecisionMap); }); }); From adc3349250cf9f912171170628cf3915275bd6ae Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 11 Dec 2020 15:25:49 -0800 Subject: [PATCH 11/11] Update comments --- .../optimizely-sdk/lib/optimizely_user_context/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts index a546ad7c0..adde48679 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts +++ b/packages/optimizely-sdk/lib/optimizely_user_context/index.ts @@ -73,12 +73,12 @@ export default class OptimizelyUserContext { } /** - * Returns a key-map of decision results for multiple flag keys and a user context. + * Returns an object of decision results for multiple flag keys and a user context. * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. * The SDK will always return key-mapped decisions. When it cannot process requests, it will return an empty map after logging the errors. * @param {string[]} keys An array of flag keys for which decisions will be made. * @param {OptimizelyDecideOptions[]} options An array of options for decision-making. - * @return {[key: string]: OptimizelyDecision} All decision results mapped by flag keys. + * @return {[key: string]: OptimizelyDecision} An object of decision results mapped by flag keys. */ decideForKeys( keys: string[], @@ -89,9 +89,9 @@ export default class OptimizelyUserContext { } /** - * Returns a key-map of decision results for all active flag keys. + * Returns an object of decision results for all active flag keys. * @param {OptimizelyDecideOptions[]} options An array of options for decision-making. - * @return {[key: string]: OptimizelyDecision} All decision results mapped by flag keys. + * @return {[key: string]: OptimizelyDecision} An object of all decision results mapped by flag keys. */ decideAll( options: OptimizelyDecideOptions[] = []