Skip to content

Commit 3d1a233

Browse files
committed
fix: Send correct impression event data for projects with duplicate rule keys (#690)
* Send correct impression event data + fix unit tests * Add getExperimentId unit tests * Add unit test for getVariationIdFromExperimentIdAndVariationKey + clean up * fix emitNotificationCenterActivate
1 parent 576b48c commit 3d1a233

File tree

7 files changed

+113
-48
lines changed

7 files changed

+113
-48
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@ describe('lib/core/decision', function() {
3535
});
3636
});
3737

38+
describe('getExperimentId method', function() {
39+
it('should return null when experiment is null', function() {
40+
var experimentId = decision.getExperimentId(rolloutDecisionObj);
41+
assert.strictEqual(experimentId, null);
42+
});
43+
44+
it('should return null when experiment is not defined', function() {
45+
var experimentId = decision.getExperimentId({});
46+
assert.strictEqual(experimentId, null);
47+
});
48+
49+
it('should return experiment id when experiment is defined', function() {
50+
var experimentId = decision.getExperimentId(featureTestDecisionObj);
51+
assert.strictEqual(experimentId, '594098');
52+
});
53+
});
54+
3855
describe('getVariationKey method', function() {
3956
it('should return empty string when variation is null', function() {
4057
var variationKey = decision.getVariationKey(rolloutDecisionObj);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,12 @@ export function getVariationKey(decisionObj: DecisionObj): string {
4242
export function getFeatureEnabledFromVariation(decisionObj: DecisionObj): boolean {
4343
return decisionObj.variation?.featureEnabled ?? false;
4444
}
45+
46+
/**
47+
* Get experiment id from the provided decision object
48+
* @param {DecisionObj} decisionObj Object representing decision
49+
* @returns {string} Experiment id or null if experiment is null
50+
*/
51+
export function getExperimentId(decisionObj: DecisionObj): string | null {
52+
return decisionObj.experiment?.id ?? null;
53+
}

packages/optimizely-sdk/lib/core/event_builder/event_helpers.tests.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { assert } from 'chai';
1818

1919
import fns from '../../utils/fns';
2020
import * as projectConfig from '../project_config';
21+
import * as decision from '../decision';
2122
import { buildImpressionEvent, buildConversionEvent } from './event_helpers';
2223

2324
describe('lib/event_builder/event_helpers', function() {
@@ -33,19 +34,21 @@ describe('lib/event_builder/event_helpers', function() {
3334
};
3435

3536
sinon.stub(projectConfig, 'getEventId');
36-
sinon.stub(projectConfig, 'getVariationIdFromExperimentAndVariationKey');
37-
sinon.stub(projectConfig, 'getExperimentId');
37+
sinon.stub(projectConfig, 'getVariationIdFromExperimentIdAndVariationKey');
3838
sinon.stub(projectConfig, 'getLayerId');
3939
sinon.stub(projectConfig, 'getAttributeId');
4040

41+
sinon.stub(decision, 'getExperimentId');
42+
4143
sinon.stub(fns, 'uuid').returns('uuid');
4244
sinon.stub(fns, 'currentTimestamp').returns(100);
4345
});
4446

4547
afterEach(function() {
48+
decision.getExperimentId.restore();
49+
4650
projectConfig.getEventId.restore();
47-
projectConfig.getVariationIdFromExperimentAndVariationKey.restore();
48-
projectConfig.getExperimentId.restore();
51+
projectConfig.getVariationIdFromExperimentIdAndVariationKey.restore();
4952
projectConfig.getLayerId.restore();
5053
projectConfig.getAttributeId.restore();
5154

@@ -56,7 +59,7 @@ describe('lib/event_builder/event_helpers', function() {
5659
describe('buildImpressionEvent', function() {
5760
describe('when botFiltering and anonymizeIP are true', function() {
5861
it('should build an ImpressionEvent with the correct attributes', function() {
59-
var decision = {
62+
var decisionObj = {
6063
experiment: {
6164
key: 'exp1',
6265
status: 'Running',
@@ -79,17 +82,19 @@ describe('lib/event_builder/event_helpers', function() {
7982
},
8083
decisionSource: 'experiment',
8184
}
82-
projectConfig.getVariationIdFromExperimentAndVariationKey
83-
.withArgs(configObj, 'exp1', 'var1')
85+
decision.getExperimentId.withArgs(decisionObj).returns('exp1-id');
86+
87+
projectConfig.getVariationIdFromExperimentIdAndVariationKey
88+
.withArgs(configObj, 'exp1-id', 'var1')
8489
.returns('var1-id');
85-
projectConfig.getExperimentId.withArgs(configObj, 'exp1').returns('exp1-id');
8690
projectConfig.getLayerId.withArgs(configObj, 'exp1-id').returns('layer-id');
8791

8892
projectConfig.getAttributeId.withArgs(configObj, 'plan_type').returns('plan_type_id');
8993

94+
9095
var result = buildImpressionEvent({
9196
configObj: configObj,
92-
decisionObj: decision,
97+
decisionObj: decisionObj,
9398
enabled: true,
9499
flagKey: 'flagkey1',
95100
userId: 'user1',
@@ -148,7 +153,7 @@ describe('lib/event_builder/event_helpers', function() {
148153

149154
describe('when botFiltering and anonymizeIP are undefined', function() {
150155
it('should create an ImpressionEvent with the correct attributes', function() {
151-
var decision = {
156+
var decisionObj = {
152157
experiment: {
153158
key: 'exp1',
154159
status: 'Running',
@@ -171,10 +176,11 @@ describe('lib/event_builder/event_helpers', function() {
171176
},
172177
decisionSource: 'experiment',
173178
}
174-
projectConfig.getVariationIdFromExperimentAndVariationKey
175-
.withArgs(configObj, 'exp1', 'var1')
179+
decision.getExperimentId.withArgs(decisionObj).returns('exp1-id');
180+
181+
projectConfig.getVariationIdFromExperimentIdAndVariationKey
182+
.withArgs(configObj, 'exp1-id', 'var1')
176183
.returns('var1-id');
177-
projectConfig.getExperimentId.withArgs(configObj, 'exp1').returns('exp1-id');
178184
projectConfig.getLayerId.withArgs(configObj, 'exp1-id').returns('layer-id');
179185

180186
projectConfig.getAttributeId.withArgs(configObj, 'plan_type').returns('plan_type_id');
@@ -184,7 +190,7 @@ describe('lib/event_builder/event_helpers', function() {
184190

185191
var result = buildImpressionEvent({
186192
configObj: configObj,
187-
decisionObj: decision,
193+
decisionObj: decisionObj,
188194
flagKey: 'flagkey1',
189195
enabled: false,
190196
userId: 'user1',

packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ import { DecisionObj } from '../decision_service';
2525
import {
2626
getAttributeId,
2727
getEventId,
28-
getExperimentId,
2928
getLayerId,
30-
getVariationIdFromExperimentAndVariationKey,
29+
getVariationIdFromExperimentIdAndVariationKey,
3130
ProjectConfig,
3231
} from '../project_config';
3332

@@ -134,10 +133,10 @@ export const buildImpressionEvent = function({
134133

135134
const ruleType = decisionObj.decisionSource;
136135
const experimentKey = decision.getExperimentKey(decisionObj);
136+
const experimentId = decision.getExperimentId(decisionObj);
137137
const variationKey = decision.getVariationKey(decisionObj);
138138

139-
const variationId = experimentKey !== '' && variationKey !== '' ? getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey) : null;
140-
const experimentId = experimentKey !== '' ? getExperimentId(configObj, experimentKey) : null;
139+
const variationId = experimentId !== null && variationKey !== '' ? getVariationIdFromExperimentIdAndVariationKey(configObj, experimentId, variationKey) : null;
141140
const layerId = experimentId !== null ? getLayerId(configObj, experimentId) : null;
142141

143142
return {

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,19 @@ describe('lib/core/project_config', function() {
392392
});
393393
});
394394

395+
describe('#getVariationIdFromExperimentIdAndVariationKey', function() {
396+
it('should return the variation id for the given experiment id and variation key', function() {
397+
assert.strictEqual(
398+
projectConfig.getVariationIdFromExperimentIdAndVariationKey(
399+
configObj,
400+
testData.experiments[0].id,
401+
testData.experiments[0].variations[0].key
402+
),
403+
testData.experiments[0].variations[0].id
404+
);
405+
});
406+
});
407+
395408
describe('#getSendFlagDecisionsValue', function() {
396409
it('should return false when sendFlagDecisions is undefined', function() {
397410
configObj.sendFlagDecisions = undefined;

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

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,26 @@ export const getVariationIdFromExperimentAndVariationKey = function(
385385
return null;
386386
};
387387

388+
/**
389+
* Get the variation ID given the experiment id and variation key
390+
* @param {ProjectConfig} projectConfig Object representing project configuration
391+
* @param {string} experimentId Id of the experiment the variation belongs to
392+
* @param {string} variationKey The variation key
393+
* @return {string|null} Variation ID or null
394+
*/
395+
export const getVariationIdFromExperimentIdAndVariationKey = function(
396+
projectConfig: ProjectConfig,
397+
experimentId: string,
398+
variationKey: string
399+
): string | null {
400+
const experiment = projectConfig.experimentIdMap[experimentId];
401+
if (experiment.variationKeyMap.hasOwnProperty(variationKey)) {
402+
return experiment.variationKeyMap[variationKey].id;
403+
}
404+
405+
return null;
406+
};
407+
388408
/**
389409
* Get experiment from provided experiment key
390410
* @param {ProjectConfig} projectConfig Object representing project configuration
@@ -719,28 +739,29 @@ export const getSendFlagDecisionsValue = function(projectConfig: ProjectConfig):
719739
}
720740

721741
export default {
722-
createProjectConfig: createProjectConfig,
723-
getExperimentId: getExperimentId,
724-
getLayerId: getLayerId,
725-
getAttributeId: getAttributeId,
726-
getEventId: getEventId,
727-
getExperimentStatus: getExperimentStatus,
728-
isActive: isActive,
729-
isRunning: isRunning,
730-
getExperimentAudienceConditions: getExperimentAudienceConditions,
731-
getVariationKeyFromId: getVariationKeyFromId,
732-
getVariationIdFromExperimentAndVariationKey: getVariationIdFromExperimentAndVariationKey,
733-
getExperimentFromKey: getExperimentFromKey,
734-
getTrafficAllocation: getTrafficAllocation,
735-
getExperimentFromId: getExperimentFromId,
736-
getFeatureFromKey: getFeatureFromKey,
737-
getVariableForFeature: getVariableForFeature,
738-
getVariableValueForVariation: getVariableValueForVariation,
739-
getTypeCastValue: getTypeCastValue,
740-
getSendFlagDecisionsValue: getSendFlagDecisionsValue,
741-
getAudiencesById: getAudiencesById,
742-
eventWithKeyExists: eventWithKeyExists,
743-
isFeatureExperiment: isFeatureExperiment,
744-
toDatafile: toDatafile,
745-
tryCreatingProjectConfig: tryCreatingProjectConfig,
742+
createProjectConfig,
743+
getExperimentId,
744+
getLayerId,
745+
getAttributeId,
746+
getEventId,
747+
getExperimentStatus,
748+
isActive,
749+
isRunning,
750+
getExperimentAudienceConditions,
751+
getVariationKeyFromId,
752+
getVariationIdFromExperimentAndVariationKey,
753+
getVariationIdFromExperimentIdAndVariationKey,
754+
getExperimentFromKey,
755+
getTrafficAllocation,
756+
getExperimentFromId,
757+
getFeatureFromKey,
758+
getVariableForFeature,
759+
getVariableValueForVariation,
760+
getTypeCastValue,
761+
getSendFlagDecisionsValue,
762+
getAudiencesById,
763+
eventWithKeyExists,
764+
isFeatureExperiment,
765+
toDatafile,
766+
tryCreatingProjectConfig,
746767
};

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -341,14 +341,15 @@ export default class Optimizely {
341341

342342
const ruleType = decisionObj.decisionSource;
343343
const experimentKey = decision.getExperimentKey(decisionObj);
344+
const experimentId = decision.getExperimentId(decisionObj);
344345
const variationKey = decision.getVariationKey(decisionObj);
345346

346-
let experimentId: string | null = null;
347-
let variationId: string | null = null;
347+
let variationId = null;
348+
let experiment;
348349

349-
if (experimentKey !== '' && variationKey !== '') {
350-
variationId = projectConfig.getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey);
351-
experimentId = projectConfig.getExperimentId(configObj, experimentKey);
350+
if (experimentId !== null && variationKey !== '') {
351+
variationId = projectConfig.getVariationIdFromExperimentIdAndVariationKey(configObj, experimentId, variationKey);
352+
experiment = configObj.experimentIdMap[experimentId];
352353
}
353354

354355
const impressionEventOptions = {
@@ -366,7 +367,6 @@ export default class Optimizely {
366367
logger: this.logger,
367368
};
368369
const impressionEvent = getImpressionEvent(impressionEventOptions);
369-
const experiment = configObj.experimentKeyMap[experimentKey];
370370
let variation;
371371
if (experiment && experiment.variationKeyMap && variationKey !== '') {
372372
variation = experiment.variationKeyMap[variationKey];

0 commit comments

Comments
 (0)