diff --git a/packages/optimizely-sdk/lib/core/bucketer/index.tests.js b/packages/optimizely-sdk/lib/core/bucketer/index.tests.js index 8fa016444..17a5ecc2d 100644 --- a/packages/optimizely-sdk/lib/core/bucketer/index.tests.js +++ b/packages/optimizely-sdk/lib/core/bucketer/index.tests.js @@ -53,7 +53,7 @@ describe('lib/core/bucketer', function() { experimentKey: configObj.experiments[0].key, trafficAllocationConfig: configObj.experiments[0].trafficAllocation, variationIdMap: configObj.variationIdMap, - experimentKeyMap: configObj.experimentKeyMap, + experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, }; @@ -101,7 +101,7 @@ describe('lib/core/bucketer', function() { experimentKey: configObj.experiments[0].key, trafficAllocationConfig: configObj.experiments[0].trafficAllocation, variationIdMap: configObj.variationIdMap, - experimentKeyMap: configObj.experimentKeyMap, + experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, }; @@ -120,7 +120,7 @@ describe('lib/core/bucketer', function() { experimentKey: configObj.experiments[4].key, trafficAllocationConfig: configObj.experiments[4].trafficAllocation, variationIdMap: configObj.variationIdMap, - experimentKeyMap: configObj.experimentKeyMap, + experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, userId: 'testUser', @@ -220,7 +220,7 @@ describe('lib/core/bucketer', function() { it('should throw an error if group ID is not in the datafile', function() { var bucketerParamsWithInvalidGroupId = cloneDeep(bucketerParams); - bucketerParamsWithInvalidGroupId.experimentKeyMap[configObj.experiments[4].key].groupId = '6969'; + bucketerParamsWithInvalidGroupId.experimentIdMap[configObj.experiments[4].id].groupId = '6969'; assert.throws(function() { bucketer.bucket(bucketerParamsWithInvalidGroupId); @@ -236,7 +236,7 @@ describe('lib/core/bucketer', function() { experimentKey: configObj.experiments[6].key, trafficAllocationConfig: configObj.experiments[6].trafficAllocation, variationIdMap: configObj.variationIdMap, - experimentKeyMap: configObj.experimentKeyMap, + experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, userId: 'testUser', @@ -282,7 +282,7 @@ describe('lib/core/bucketer', function() { }, ], variationIdMap: configObj.variationIdMap, - experimentKeyMap: configObj.experimentKeyMap, + experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, }; @@ -322,7 +322,7 @@ describe('lib/core/bucketer', function() { }, ], variationIdMap: configObj.variationIdMap, - experimentKeyMap: configObj.experimentKeyMap, + experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, }; @@ -370,7 +370,7 @@ describe('lib/core/bucketer', function() { bucketerParams = { trafficAllocationConfig: configObj.experiments[0].trafficAllocation, variationIdMap: configObj.variationIdMap, - experimentKeyMap: configObj.experimentKeyMap, + experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, }; diff --git a/packages/optimizely-sdk/lib/core/bucketer/index.ts b/packages/optimizely-sdk/lib/core/bucketer/index.ts index 74b22a68d..063602af6 100644 --- a/packages/optimizely-sdk/lib/core/bucketer/index.ts +++ b/packages/optimizely-sdk/lib/core/bucketer/index.ts @@ -58,7 +58,7 @@ const RANDOM_POLICY = 'random'; export const bucket = function(bucketerParams: BucketerParams): DecisionResponse { const decideReasons: string[] = []; // Check if user is in a random group; if so, check if user is bucketed into a specific experiment - const experiment = bucketerParams.experimentKeyMap[bucketerParams.experimentKey]; + const experiment = bucketerParams.experimentIdMap[bucketerParams.experimentId]; const groupId = experiment['groupId']; if (groupId) { const group = bucketerParams.groupIdMap[groupId]; diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js index 558da7ab7..ae2c06c43 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js @@ -44,6 +44,7 @@ describe('lib/core/decision_service', function() { var decisionServiceInstance; var mockLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); var bucketerStub; + var experiment; beforeEach(function() { bucketerStub = sinon.stub(bucketer, 'bucket'); @@ -64,18 +65,20 @@ describe('lib/core/decision_service', function() { result: '111128', reasons: [], }; + experiment = configObj.experimentIdMap['111127']; bucketerStub.returns(fakeDecisionResponse); // contains variation ID of the 'control' variation from `test_data` assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user').result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user').result ); sinon.assert.calledOnce(bucketerStub); }); it('should return the whitelisted variation if the user is whitelisted', function() { + experiment = configObj.experimentIdMap['122227']; assert.strictEqual( 'variationWithAudience', - decisionServiceInstance.getVariation(configObj, 'testExperimentWithAudiences', 'user2').result + decisionServiceInstance.getVariation(configObj, experiment, 'user2').result ); sinon.assert.notCalled(bucketerStub); assert.strictEqual(2, mockLogger.log.callCount); @@ -90,8 +93,9 @@ describe('lib/core/decision_service', function() { }); it('should return null if the user does not meet audience conditions', function() { + experiment = configObj.experimentIdMap['122227']; assert.isNull( - decisionServiceInstance.getVariation(configObj, 'testExperimentWithAudiences', 'user3', { foo: 'bar' }).result + decisionServiceInstance.getVariation(configObj, experiment, 'user3', { foo: 'bar' }).result ); assert.strictEqual(4, mockLogger.log.callCount); assert.strictEqual( @@ -113,7 +117,8 @@ describe('lib/core/decision_service', function() { }); it('should return null if the experiment is not running', function() { - assert.isNull(decisionServiceInstance.getVariation(configObj, 'testExperimentNotRunning', 'user1').result); + experiment = configObj.experimentIdMap['133337']; + assert.isNull(decisionServiceInstance.getVariation(configObj, experiment, 'user1').result); sinon.assert.notCalled(bucketerStub); assert.strictEqual(1, mockLogger.log.callCount); assert.strictEqual( @@ -128,6 +133,7 @@ describe('lib/core/decision_service', function() { result: '111128', reasons: [], }; + experiment = configObj.experimentIdMap['111127']; bucketerStub.returns(fakeDecisionResponse); // ID of the 'control' variation from `test_data` var attributes = { $opt_experiment_bucket_map: { @@ -139,7 +145,7 @@ describe('lib/core/decision_service', function() { assert.strictEqual( 'variation', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user', attributes).result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user', attributes).result ); sinon.assert.notCalled(bucketerStub); }); @@ -187,10 +193,11 @@ describe('lib/core/decision_service', function() { }, }, }); + experiment = configObj.experimentIdMap['111127']; assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user').result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user').result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); @@ -210,10 +217,11 @@ describe('lib/core/decision_service', function() { user_id: 'decision_service_user', experiment_bucket_map: {}, }); + experiment = configObj.experimentIdMap['111127']; assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user').result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user').result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); @@ -231,10 +239,11 @@ describe('lib/core/decision_service', function() { it('should bucket if the user profile service returns null', function() { bucketerStub.returns(fakeDecisionResponse); // ID of the 'control' variation userProfileLookupStub.returns(null); + experiment = configObj.experimentIdMap['111127']; assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user').result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user').result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); @@ -259,10 +268,11 @@ describe('lib/core/decision_service', function() { }, }, }); + experiment = configObj.experimentIdMap['111127']; assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user').result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user').result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); @@ -291,10 +301,11 @@ describe('lib/core/decision_service', function() { user_id: 'decision_service_user', experiment_bucket_map: {}, // no decisions for user }); + experiment = configObj.experimentIdMap['111127']; assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user').result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user').result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); @@ -320,10 +331,11 @@ describe('lib/core/decision_service', function() { it('should log an error message if "lookup" throws an error', function() { bucketerStub.returns(fakeDecisionResponse); // ID of the 'control' variation userProfileLookupStub.throws(new Error('I am an error')); + experiment = configObj.experimentIdMap['111127']; assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user').result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user').result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); // should still go through with bucketing @@ -341,10 +353,11 @@ describe('lib/core/decision_service', function() { bucketerStub.returns(fakeDecisionResponse); // ID of the 'control' variation userProfileLookupStub.returns(null); userProfileSaveStub.throws(new Error('I am an error')); + experiment = configObj.experimentIdMap['111127']; assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user').result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user').result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); // should still go through with bucketing @@ -389,9 +402,11 @@ describe('lib/core/decision_service', function() { }, }; + experiment = configObj.experimentIdMap['111127']; + assert.strictEqual( 'variation', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user', attributes).result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user', attributes).result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); @@ -416,6 +431,8 @@ describe('lib/core/decision_service', function() { }, }); + experiment = configObj.experimentIdMap['111127']; + var attributes = { $opt_experiment_bucket_map: { '122227': { @@ -427,7 +444,7 @@ describe('lib/core/decision_service', function() { assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user', attributes).result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user', attributes).result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); @@ -452,6 +469,8 @@ describe('lib/core/decision_service', function() { }, }); + experiment = configObj.experimentIdMap['111127']; + var attributes = { $opt_experiment_bucket_map: { '111127': { @@ -463,7 +482,7 @@ describe('lib/core/decision_service', function() { assert.strictEqual( 'variation', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user', attributes).result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user', attributes).result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); @@ -480,6 +499,8 @@ describe('lib/core/decision_service', function() { it('should use attributes when the userProfileLookup returns null', function() { userProfileLookupStub.returns(null); + experiment = configObj.experimentIdMap['111127']; + var attributes = { $opt_experiment_bucket_map: { '111127': { @@ -490,7 +511,7 @@ describe('lib/core/decision_service', function() { assert.strictEqual( 'variation', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'decision_service_user', attributes).result + decisionServiceInstance.getVariation(configObj, experiment, 'decision_service_user', attributes).result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); @@ -509,9 +530,10 @@ describe('lib/core/decision_service', function() { describe('buildBucketerParams', function() { it('should return params object with correct properties', function() { + experiment = configObj.experimentIdMap['111127']; var bucketerParams = decisionServiceInstance.buildBucketerParams( configObj, - 'testExperiment', + experiment, 'testUser', 'testUser' ); @@ -533,6 +555,7 @@ describe('lib/core/decision_service', function() { ], variationIdMap: configObj.variationIdMap, logger: mockLogger, + experimentIdMap: configObj.experimentIdMap, experimentKeyMap: configObj.experimentKeyMap, groupIdMap: configObj.groupIdMap, }; @@ -565,12 +588,12 @@ describe('lib/core/decision_service', function() { }); it('should return decision response with result true when audience conditions are met', function() { + experiment = configObj.experimentIdMap['122227']; assert.isTrue( decisionServiceInstance.checkIfUserIsInAudience( configObj, - 'testExperimentWithAudiences', + experiment, "experiment", - 'testUser', { browser_type: 'firefox' }, '' ).result @@ -587,12 +610,12 @@ describe('lib/core/decision_service', function() { }); it('should return decision response with result true when experiment has no audience', function() { + experiment = configObj.experimentIdMap['111127']; assert.isTrue( decisionServiceInstance.checkIfUserIsInAudience( configObj, - 'testExperiment', - "experiment", - 'testUser', + experiment, + 'experiment', {}, '' ).result @@ -611,12 +634,12 @@ describe('lib/core/decision_service', function() { }); it('should return decision response with result false when audience conditions can not be evaluated', function() { + experiment = configObj.experimentIdMap['122227']; assert.isFalse( decisionServiceInstance.checkIfUserIsInAudience( configObj, - 'testExperimentWithAudiences', + experiment, "experiment", - 'testUser', {}, '' ).result @@ -635,12 +658,12 @@ describe('lib/core/decision_service', function() { }); it('should return decision response with result false when audience conditions are not met', function() { + experiment = configObj.experimentIdMap['122227']; assert.isFalse( decisionServiceInstance.checkIfUserIsInAudience( configObj, - 'testExperimentWithAudiences', + experiment, "experiment", - 'testUser', { browser_type: 'chrome' }, '' ).result @@ -1046,6 +1069,7 @@ describe('lib/core/decision_service', function() { }, }, }); + var experiment = configObj.experimentIdMap['111127']; var decisionServiceInstance = createDecisionService({ logger: createdLogger, @@ -1054,7 +1078,7 @@ describe('lib/core/decision_service', function() { assert.strictEqual( 'control', - decisionServiceInstance.getVariation(configObj, 'testExperiment', 'test_user', userAttributesWithBucketingId).result + decisionServiceInstance.getVariation(configObj, experiment, 'test_user', userAttributesWithBucketingId).result ); sinon.assert.calledWithExactly(userProfileLookupStub, 'test_user'); }); @@ -1139,14 +1163,16 @@ describe('lib/core/decision_service', function() { describe('user bucketed into this experiment', function() { var getVariationStub; + var experiment; beforeEach(function() { fakeDecisionResponseWithArgs = { result: 'variation', reasons: [], }; + experiment = configObj.experimentIdMap['594098']; getVariationStub = sandbox.stub(decisionServiceInstance, 'getVariation'); getVariationStub.returns(fakeDecisionResponse); - getVariationStub.withArgs(configObj, 'testing_my_feature', 'user1').returns(fakeDecisionResponseWithArgs); + getVariationStub.withArgs(configObj, experiment, 'user1').returns(fakeDecisionResponseWithArgs); }); it('returns a decision with a variation in the experiment the feature is attached to', function() { @@ -1366,7 +1392,8 @@ describe('lib/core/decision_service', function() { sinon.assert.calledWithExactly( getVariationStub, configObj, - 'testing_my_feature', 'user1', + experiment, + 'user1', { test_attribute: 'test_value', }, @@ -2443,8 +2470,8 @@ describe('lib/core/decision_service', function() { decisionService.getVariationForRollout(configObj, feature, 'testUser', attributes).result; sinon.assert.callCount(buildBucketerParamsSpy, 2); - sinon.assert.calledWithExactly(buildBucketerParamsSpy, configObj, '594031', 'testUser', 'testUser'); - sinon.assert.calledWithExactly(buildBucketerParamsSpy, configObj, '594037', 'testUser', 'testUser'); + sinon.assert.calledWithExactly(buildBucketerParamsSpy, configObj, configObj.experimentIdMap['594031'], 'testUser', 'testUser'); + sinon.assert.calledWithExactly(buildBucketerParamsSpy, configObj, configObj.experimentIdMap['594037'], 'testUser', 'testUser'); }); it('should call buildBucketerParams with bucketing Id when bucketing Id is provided in the attributes', function() { @@ -2455,8 +2482,8 @@ describe('lib/core/decision_service', function() { decisionService.getVariationForRollout(configObj, feature, 'testUser', attributes).result; sinon.assert.callCount(buildBucketerParamsSpy, 2); - sinon.assert.calledWithExactly(buildBucketerParamsSpy, configObj, '594031', 'abcdefg', 'testUser'); - sinon.assert.calledWithExactly(buildBucketerParamsSpy, configObj, '594037', 'abcdefg', 'testUser'); + sinon.assert.calledWithExactly(buildBucketerParamsSpy, configObj, configObj.experimentIdMap['594031'], 'abcdefg', 'testUser'); + sinon.assert.calledWithExactly(buildBucketerParamsSpy, configObj, configObj.experimentIdMap['594037'], 'abcdefg', 'testUser'); }); }); }); diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.ts b/packages/optimizely-sdk/lib/core/decision_service/index.ts index 2d235a36c..df577c78a 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.ts +++ b/packages/optimizely-sdk/lib/core/decision_service/index.ts @@ -31,7 +31,6 @@ import { getExperimentAudienceConditions, getExperimentFromId, getExperimentFromKey, - getExperimentId, getTrafficAllocation, getVariationIdFromExperimentAndVariationKey, getVariationKeyFromId, @@ -107,7 +106,7 @@ export class DecisionService { */ getVariation( configObj: ProjectConfig, - experimentKey: string, + experiment: Experiment, userId: string, attributes?: UserAttributes, options: { [key: string]: boolean } = {} @@ -115,7 +114,7 @@ export class DecisionService { // by default, the bucketing ID should be the user ID const bucketingId = this.getBucketingId(userId, attributes); const decideReasons = []; - + const experimentKey = experiment.key; if (!this.checkIfExperimentIsActive(configObj, experimentKey)) { const experimentNotRunningLogMessage = sprintf(LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey); this.logger.log(LOG_LEVEL.INFO, experimentNotRunningLogMessage); @@ -125,7 +124,6 @@ export class DecisionService { reasons: decideReasons, }; } - const experiment = configObj.experimentKeyMap[experimentKey]; const decisionForcedVariation = this.getForcedVariation(configObj, experimentKey, userId); decideReasons.push(...decisionForcedVariation.reasons); const forcedVariationKey = decisionForcedVariation.result; @@ -139,7 +137,6 @@ export class DecisionService { const decisionWhitelistedVariation = this.getWhitelistedVariation(experiment, userId); decideReasons.push(...decisionWhitelistedVariation.reasons); let variation = decisionWhitelistedVariation.result; - if (variation) { return { result: variation.key, @@ -176,9 +173,8 @@ export class DecisionService { // Perform regular targeting and bucketing const decisionifUserIsInAudience = this.checkIfUserIsInAudience( configObj, - experimentKey, + experiment, AUDIENCE_EVALUATION_TYPES.EXPERIMENT, - userId, attributes, '' ); @@ -198,7 +194,7 @@ export class DecisionService { }; } - const bucketerParams = this.buildBucketerParams(configObj, experimentKey, bucketingId, userId); + const bucketerParams = this.buildBucketerParams(configObj, experiment, bucketingId, userId); const decisionVariation = bucket(bucketerParams); decideReasons.push(...decisionVariation.reasons); const variationId = decisionVariation.result; @@ -329,20 +325,19 @@ export class DecisionService { */ private checkIfUserIsInAudience( configObj: ProjectConfig, - experimentKey: string, + experiment: Experiment, evaluationAttribute: string, - userId: string, attributes?: UserAttributes, - loggingKey?: string | number + loggingKey?: string | number, ): DecisionResponse { const decideReasons: string[] = []; - const experimentAudienceConditions = getExperimentAudienceConditions(configObj, experimentKey); + const experimentAudienceConditions = getExperimentAudienceConditions(configObj, experiment.id); const audiencesById = getAudiencesById(configObj); const evaluatingAudiencesCombinedMessage = sprintf( LOG_MESSAGES.EVALUATING_AUDIENCES_COMBINED, MODULE_NAME, evaluationAttribute, - loggingKey || experimentKey, + loggingKey || experiment.key, JSON.stringify(experimentAudienceConditions) ); this.logger.log( @@ -355,7 +350,7 @@ export class DecisionService { LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, MODULE_NAME, evaluationAttribute, - loggingKey || experimentKey, + loggingKey || experiment.key, result.toString().toUpperCase() ); this.logger.log( @@ -380,18 +375,19 @@ export class DecisionService { */ private buildBucketerParams( configObj: ProjectConfig, - experimentKey: string, + experiment: Experiment, bucketingId: string, userId: string ): BucketerParams { return { bucketingId, - experimentId: getExperimentId(configObj, experimentKey), - experimentKey, + experimentId: experiment.id, + experimentKey: experiment.key, + experimentIdMap: configObj.experimentIdMap, experimentKeyMap: configObj.experimentKeyMap, groupIdMap: configObj.groupIdMap, logger: this.logger, - trafficAllocationConfig: getTrafficAllocation(configObj, experimentKey), + trafficAllocationConfig: getTrafficAllocation(configObj, experiment.id), userId, variationIdMap: configObj.variationIdMap, } @@ -573,7 +569,7 @@ export class DecisionService { for (index = 0; index < feature.experimentIds.length; index++) { const experiment = getExperimentFromId(configObj, feature.experimentIds[index], this.logger); if (experiment) { - decisionVariation = this.getVariation(configObj, experiment.key, userId, attributes, options); + decisionVariation = this.getVariation(configObj, experiment, userId, attributes, options); decideReasons.push(...decisionVariation.reasons); variationKey = decisionVariation.result; if (variationKey) { @@ -686,13 +682,12 @@ export class DecisionService { let decisionVariation; let decisionifUserIsInAudience; for (let index = 0; index < endIndex; index++) { - rolloutRule = configObj.experimentKeyMap[rollout.experiments[index].key]; + rolloutRule = configObj.experimentIdMap[rollout.experiments[index].id]; loggingKey = index + 1; decisionifUserIsInAudience = this.checkIfUserIsInAudience( configObj, - rolloutRule.key, + rolloutRule, AUDIENCE_EVALUATION_TYPES.RULE, - userId, attributes, loggingKey ); @@ -723,7 +718,7 @@ export class DecisionService { userMeetsConditionsForTargetingRuleMessage ); decideReasons.push(userMeetsConditionsForTargetingRuleMessage); - bucketerParams = this.buildBucketerParams(configObj, rolloutRule.key, bucketingId, userId); + bucketerParams = this.buildBucketerParams(configObj, rolloutRule, bucketingId, userId); decisionVariation = bucket(bucketerParams); decideReasons.push(...decisionVariation.reasons); variationId = decisionVariation.result; @@ -768,9 +763,8 @@ export class DecisionService { const everyoneElseRule = configObj.experimentKeyMap[rollout.experiments[endIndex].key]; const decisionifUserIsInEveryoneRule = this.checkIfUserIsInAudience( configObj, - everyoneElseRule.key, + everyoneElseRule, AUDIENCE_EVALUATION_TYPES.RULE, - userId, attributes, 'Everyone Else' ); @@ -786,7 +780,7 @@ export class DecisionService { userMeetsConditionsForEveryoneTargetingRuleMessage ); decideReasons.push(userMeetsConditionsForEveryoneTargetingRuleMessage); - bucketerParams = this.buildBucketerParams(configObj, everyoneElseRule.key, bucketingId, userId); + bucketerParams = this.buildBucketerParams(configObj, everyoneElseRule, bucketingId, userId); decisionVariation = bucket(bucketerParams); decideReasons.push(...decisionVariation.reasons); variationId = decisionVariation.result; diff --git a/packages/optimizely-sdk/lib/core/project_config/index.tests.js b/packages/optimizely-sdk/lib/core/project_config/index.tests.js index 8209359f2..f0b922ed3 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.tests.js @@ -368,15 +368,15 @@ describe('lib/core/project_config', function() { it('should retrieve traffic allocation given valid experiment key in getTrafficAllocation', function() { assert.deepEqual( - projectConfig.getTrafficAllocation(configObj, testData.experiments[0].key), + projectConfig.getTrafficAllocation(configObj, testData.experiments[0].id), testData.experiments[0].trafficAllocation ); }); it('should throw error for invalid experient key in getTrafficAllocation', function() { assert.throws(function() { - projectConfig.getTrafficAllocation(configObj, 'invalidExperimentKey'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + projectConfig.getTrafficAllocation(configObj, 'invalidExperimentId'); + }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); }); describe('#getVariationIdFromExperimentAndVariationKey', function() { @@ -670,7 +670,7 @@ describe('lib/core/project_config', function() { describe('#getExperimentAudienceConditions', function() { it('should retrieve audiences for valid experiment key', function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); - assert.deepEqual(projectConfig.getExperimentAudienceConditions(configObj, testData.experiments[1].key), [ + assert.deepEqual(projectConfig.getExperimentAudienceConditions(configObj, testData.experiments[1].id), [ '11154', ]); }); @@ -678,13 +678,13 @@ describe('lib/core/project_config', function() { it('should throw error for invalid experiment key', function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); assert.throws(function() { - projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentKey'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentId'); + }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); }); it('should return experiment audienceIds if experiment has no audienceConditions', function() { configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); - var result = projectConfig.getExperimentAudienceConditions(configObj, 'feat_with_var_test'); + var result = projectConfig.getExperimentAudienceConditions(configObj, '11564051718'); assert.deepEqual(result, [ '3468206642', '3988293898', @@ -700,7 +700,7 @@ describe('lib/core/project_config', function() { configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); // audience_combinations_experiment has both audienceConditions and audienceIds // audienceConditions should be preferred over audienceIds - var result = projectConfig.getExperimentAudienceConditions(configObj, 'audience_combinations_experiment'); + var result = projectConfig.getExperimentAudienceConditions(configObj, '1323241598'); assert.deepEqual(result, [ 'and', ['or', '3468206642', '3988293898'], diff --git a/packages/optimizely-sdk/lib/core/project_config/index.ts b/packages/optimizely-sdk/lib/core/project_config/index.ts index 64b5ca8f6..360919d14 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.ts +++ b/packages/optimizely-sdk/lib/core/project_config/index.ts @@ -333,7 +333,7 @@ export const isRunning = function(projectConfig: ProjectConfig, experimentKey: s /** * Get audience conditions for the experiment * @param {ProjectConfig} projectConfig Object representing project configuration - * @param {string} experimentKey Experiment key for which audience conditions are to be determined + * @param {string} experimentId Experiment id for which audience conditions are to be determined * @return {Array} Audience conditions for the experiment - can be an array of audience IDs, or a * nested array of conditions * Examples: ["5", "6"], ["and", ["or", "1", "2"], "3"] @@ -341,11 +341,11 @@ export const isRunning = function(projectConfig: ProjectConfig, experimentKey: s */ export const getExperimentAudienceConditions = function( projectConfig: ProjectConfig, - experimentKey: string + experimentId: string ): Array { - const experiment = projectConfig.experimentKeyMap[experimentKey]; + const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); } return experiment.audienceConditions || experiment.audienceIds; @@ -404,16 +404,16 @@ export const getExperimentFromKey = function(projectConfig: ProjectConfig, exper }; /** - * Given an experiment key, returns the traffic allocation within that experiment + * Given an experiment id, returns the traffic allocation within that experiment * @param {ProjectConfig} projectConfig Object representing project configuration - * @param {string} experimentKey Key representing the experiment + * @param {string} experimentId Id representing the experiment * @return {TrafficAllocation[]} Traffic allocation for the experiment * @throws If experiment key is not in datafile */ -export const getTrafficAllocation = function(projectConfig: ProjectConfig, experimentKey: string): TrafficAllocation[]{ - const experiment = projectConfig.experimentKeyMap[experimentKey]; +export const getTrafficAllocation = function(projectConfig: ProjectConfig, experimentId: string): TrafficAllocation[] { + const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); } return experiment.trafficAllocation; }; diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 924e56cd4..99915d4e9 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -81,11 +81,11 @@ type StringInputs = Partial>; */ export default class Optimizely { private isOptimizelyConfigValid: boolean; - private disposeOnUpdate: (() => void ) | null; + private disposeOnUpdate: (() => void) | null; private readyPromise: Promise<{ success: boolean; reason?: string }>; // readyTimeout is specified as any to make this work in both browser & Node // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readyTimeouts: { [key: string]: {readyTimeout: any; onClose:() => void} }; + private readyTimeouts: { [key: string]: { readyTimeout: any; onClose: () => void } }; private nextReadyTimeoutId: number; private clientEngine: string; private clientVersion: string; @@ -346,7 +346,7 @@ export default class Optimizely { let experimentId: string | null = null; let variationId: string | null = null; - if (experimentKey !=='' && variationKey !== '') { + if (experimentKey !== '' && variationKey !== '') { variationId = projectConfig.getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey); experimentId = projectConfig.getExperimentId(configObj, experimentKey); } @@ -506,7 +506,7 @@ export default class Optimizely { return null; } - const variationKey = this.decisionService.getVariation(configObj, experimentKey, userId, attributes).result; + const variationKey = this.decisionService.getVariation(configObj, experiment, userId, attributes).result; const decisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) ? DECISION_NOTIFICATION_TYPES.FEATURE_TEST : DECISION_NOTIFICATION_TYPES.AB_TEST; @@ -1047,7 +1047,7 @@ export default class Optimizely { * of the variable */ getFeatureVariableDouble( - featureKey:string, + featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes @@ -1518,20 +1518,20 @@ export default class Optimizely { if (!allDecideOptions[OptimizelyDecideOption.EXCLUDE_VARIABLES]) { feature.variables.forEach(variable => { variablesMap[variable.key] = - this.getFeatureVariableValueFromVariation( - key, - flagEnabled, - decisionObj.variation, - variable, - userId - ); + this.getFeatureVariableValueFromVariation( + key, + flagEnabled, + decisionObj.variation, + variable, + userId + ); }); } if ( !allDecideOptions[OptimizelyDecideOption.DISABLE_DECISION_EVENT] && ( - decisionSource === DECISION_SOURCES.FEATURE_TEST || - decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj)) + decisionSource === DECISION_SOURCES.FEATURE_TEST || + decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj)) ) { this.sendImpressionEvent( decisionObj, @@ -1544,7 +1544,7 @@ export default class Optimizely { } const shouldIncludeReasons = allDecideOptions[OptimizelyDecideOption.INCLUDE_REASONS]; - const reportedReasons = shouldIncludeReasons ? reasons: []; + const reportedReasons = shouldIncludeReasons ? reasons : []; const featureInfo = { flagKey: key, @@ -1580,7 +1580,7 @@ export default class Optimizely { * @return {[key: string]: boolean} Map of all provided decide options including default decide options */ private getAllDecideOptions(options: OptimizelyDecideOption[]): { [key: string]: boolean } { - const allDecideOptions = {...this.defaultDecideOptions}; + const allDecideOptions = { ...this.defaultDecideOptions }; if (!Array.isArray(options)) { this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.INVALID_DECIDE_OPTIONS, MODULE_NAME)); } else { diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index 347f43c64..f420268e5 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -21,6 +21,7 @@ export interface BucketerParams { userId: string; trafficAllocationConfig: TrafficAllocation[]; experimentKeyMap: { [key: string]: Experiment }; + experimentIdMap: { [id: string]: Experiment }; groupIdMap: { [key: string]: Group }; variationIdMap: { [id: string]: Variation }; logger: LogHandler;