Skip to content

Commit 8b2177b

Browse files
authored
fix: Correct decision notification payload type for getVariation feature test decision (#375)
Summary: In createProjectConfig, we attempt to create an object experimentFeatureMap whose keys are experiment IDs, and whose values are arrays of feature IDs. We loop over each experiment ID in each feature flag's experimentIds, and update experimentFeatureMap accordingly. The problem occurs when one of these experiments is in a group (had a groupId property). The inner loop breaks out by returning false (this early iteration exit by returning false is a feature of lodash forEach). Then, any remaining experiment IDs are wrongfully excluded from experimentFeatureMap. For any of these skipped feature experiment IDs, the isFeatureExperiment function will wrongly return false, leading to an incorrect decision notification payload. With this change, the loop always runs over all experiment IDs of each feature. The previous behavior of only assigning feature.groupId once is maintained. Test plan: New unit tests for isFeatureExperiment function of project config module Issues: https://optimizely.atlassian.net/browse/OASIS-5759
1 parent 9a4d96b commit 8b2177b

File tree

3 files changed

+116
-5
lines changed

3 files changed

+116
-5
lines changed

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,15 @@ module.exports = {
9898
fns.forEach(feature.experimentIds || [], function(experimentId) {
9999
// Add this experiment in experiment-feature map.
100100
if (projectConfig.experimentFeatureMap[experimentId]) {
101-
projectConfig.experimentFeatureMap[experimentId].push(feature.id);
101+
projectConfig.experimentFeatureMap[experimentId].push(feature.id);
102102
} else {
103103
projectConfig.experimentFeatureMap[experimentId] = [feature.id];
104104
}
105-
105+
106106
var experimentInFeature = projectConfig.experimentIdMap[experimentId];
107-
if (experimentInFeature.groupId) {
107+
// Experiments in feature can only belong to one mutex group.
108+
if (experimentInFeature.groupId && !feature.groupId) {
108109
feature.groupId = experimentInFeature.groupId;
109-
// Experiments in feature can only belong to one mutex group.
110-
return false;
111110
}
112111
});
113112
});

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,28 @@ describe('lib/core/project_config', function() {
559559
assert.deepEqual(result, ['and', ['or', '3468206642', '3988293898'], ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643']]);
560560
});
561561
});
562+
563+
describe('#isFeatureExperiment', function() {
564+
it('returns true for a feature test', function() {
565+
var config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures());
566+
var result = projectConfig.isFeatureExperiment(config, '594098'); // id of 'testing_my_feature'
567+
assert.isTrue(result);
568+
});
569+
570+
it('returns false for an A/B test', function() {
571+
var config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfig());
572+
var result = projectConfig.isFeatureExperiment(config, '111127'); // id of 'testExperiment'
573+
assert.isFalse(result);
574+
});
575+
576+
it('returns true for a feature test in a mutex group', function() {
577+
var config = projectConfig.createProjectConfig(testDatafile.getMutexFeatureTestsConfig());
578+
var result = projectConfig.isFeatureExperiment(config, '17128410791'); // id of 'f_test1'
579+
assert.isTrue(result);
580+
result = projectConfig.isFeatureExperiment(config, '17139931304'); // id of 'f_test2'
581+
assert.isTrue(result);
582+
});
583+
});
562584
});
563585

564586
describe('#tryCreatingProjectConfig', function() {

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

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,6 +2489,95 @@ var typedAudiencesById = {
24892489
},
24902490
};
24912491

2492+
var mutexFeatureTestsConfig = {
2493+
version: '4',
2494+
rollouts: [
2495+
{
2496+
experiments: [
2497+
{
2498+
status: 'Not started',
2499+
audienceIds: [],
2500+
variations: [{ variables: [], id: '17138530965', key: '17138530965', featureEnabled: false }],
2501+
id: '17138130490',
2502+
key: '17138130490',
2503+
layerId: '17151011617',
2504+
trafficAllocation: [{ entityId: '17138530965', endOfRange: 0 }],
2505+
forcedVariations: {},
2506+
}
2507+
],
2508+
id: '17151011617',
2509+
}
2510+
],
2511+
typedAudiences: [],
2512+
anonymizeIP: false,
2513+
projectId: '1715448053799999',
2514+
variables: [],
2515+
featureFlags: [
2516+
{
2517+
experimentIds: ['17128410791', '17139931304'],
2518+
rolloutId: '17151011617',
2519+
variables: [],
2520+
id: '17146211047',
2521+
key: 'f',
2522+
},
2523+
],
2524+
experiments: [],
2525+
audiences: [],
2526+
groups: [
2527+
{
2528+
policy: 'random',
2529+
trafficAllocation: [
2530+
{ entityId: '17139931304', endOfRange: 9900 },
2531+
{ entityId: '17128410791', endOfRange: 10000 }
2532+
],
2533+
experiments: [
2534+
{
2535+
status: 'Running',
2536+
audienceIds: [],
2537+
variations: [
2538+
{ variables: [], id: 17155031309, key: 'variation_1', featureEnabled: false },
2539+
{ variables: [], id: 17124610952, key: 'variation_2', featureEnabled: true }
2540+
],
2541+
id: '17139931304',
2542+
key: 'f_test2',
2543+
layerId: '17149391594',
2544+
trafficAllocation: [
2545+
{ entityId: '17155031309', endOfRange: 5000 },
2546+
{ entityId: '17124610952', endOfRange: 10000 }
2547+
],
2548+
forcedVariations: {}
2549+
},
2550+
{
2551+
status: 'Running',
2552+
audienceIds: [],
2553+
variations: [
2554+
{ variables: [], id: '17175820099', key: 'variation_1', featureEnabled: false },
2555+
{ variables: [], id: '17144050391', key: 'variation_2', featureEnabled: true }
2556+
],
2557+
id: '17128410791',
2558+
key: 'f_test1',
2559+
layerId: '17145581153',
2560+
trafficAllocation: [
2561+
{ entityId: '17175820099', endOfRange: 5000 },
2562+
{ entityId: '17144050391', endOfRange: 10000 }
2563+
],
2564+
forcedVariations: {}
2565+
}
2566+
],
2567+
id: '17142090293'
2568+
}
2569+
],
2570+
attributes: [],
2571+
botFiltering: false,
2572+
accountId: '4879520872999',
2573+
events: [{ experimentIds: ['17128410791', '17139931304'], id: '17140380990', key: 'e' }],
2574+
revision: '12',
2575+
};
2576+
2577+
var getMutexFeatureTestsConfig = function() {
2578+
return cloneDeep(mutexFeatureTestsConfig);
2579+
};
2580+
24922581
module.exports = {
24932582
getTestProjectConfig: getTestProjectConfig,
24942583
getParsedAudiences: getParsedAudiences,
@@ -2497,4 +2586,5 @@ module.exports = {
24972586
getUnsupportedVersionConfig: getUnsupportedVersionConfig,
24982587
getTypedAudiencesConfig: getTypedAudiencesConfig,
24992588
typedAudiencesById: typedAudiencesById,
2589+
getMutexFeatureTestsConfig: getMutexFeatureTestsConfig,
25002590
};

0 commit comments

Comments
 (0)