8000 feat(flag-decisions): Add support for sending flag decisions (#599) · rserrano-eSW/javascript-sdk@6f60a6b · GitHub
[go: up one dir, main page]

Skip to content

Commit 6f60a6b

Browse files
yavoronaMatt Carroll
and
Matt Carroll
authored
feat(flag-decisions): Add support for sending flag decisions (optimizely#599)
* Update activate and isFeatureEnabled to send decision events Include metaData in ImpressionEvent Send decisions when variation is null Update existing event_helpers tests Add metadata to impressionEventParams in getImpressionEventParams method Add ruleKey parameter to ImpressionEvent Change experimentKey to ruleKey Update existing unit tests Remove sendFlagDecisions from common event params Update event-processor to include metadata in makeDecisionSnapshot Update event-processos variationKey type to string | null Update variationKey type to string | null Update existing optimizely module tests part 1 Start adding new tests Update unit tests impression params when decision.experiment is undefined Clean up Change layer id type to be string | null Update variationId to bull as default * Upgrade event-processor to 0.7.0 * Add createEventProcessor factory function * Add tests for getSendFlagDecisionsValue method * Update variationKey type to be string * Refactor isFeatureEnabled * Pass decision object instead of 4 individual values * Update DecisionService setForcedVariation signature and comments * Use optional chaining and nullish coalescing operator * Create decision helper functions * Address comments * Clean up * Fix experiment decision object * Change variation_key to '' instead of null in metadata * Update Changelog Co-authored-by: Matt Carroll <matt.carroll@optimizely.com>
1 parent 6c62080 commit 6f60a6b

24 files changed

+752
-105
lines changed

packages/optimizely-sdk/CHANGELOG.MD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
9+
- Added support sending impression events every time a decision is made ([#599](https://github.com/optimizely/javascript-sdk/pull/599))
910

1011
## [4.3.4] - October 8, 2020
1112

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Copyright 2020, Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { assert } from 'chai';
17+
import { rolloutDecisionObj, featureTestDecisionObj } from '../../tests/test_data';
18+
import * as decision from './';
19+
20+
describe('lib/core/decision', function() {
21+
describe('getExperimentKey method', function() {
22+
it('should return empty string when experiment is null', function() {
23+
var experimentKey = decision.getExperimentKey(rolloutDecisionObj);
24+
assert.strictEqual(experimentKey, '');
25+
});
26+
27+
it('should return empty string when experiment is not defined', function() {
28+
var experimentKey = decision.getExperimentKey({});
29+
assert.strictEqual(experimentKey, '');
30+
});
31+
32+
it('should return experiment key when experiment is defined', function() {
33+
var experimentKey = decision.getExperimentKey(featureTestDecisionObj);
34+
assert.strictEqual(experimentKey, 'testing_my_feature');
35+
});
36+
});
37+
38+
describe('getVariationKey method', function() {
39+
it('should return empty string when variation is null', function() {
40+
var variationKey = decision.getVariationKey(rolloutDecisionObj);
41+
assert.strictEqual(variationKey, '');
42+
});
43+
44+
it('should return empty string when variation is not defined', function() {
45+
var variationKey = decision.getVariationKey({});
46+
assert.strictEqual(variationKey, '');
47+
});
48+
49+
it('should return variation key when variation is defined', function() {
50+
var variationKey = decision.getVariationKey(featureTestDecisionObj);
51+
assert.strictEqual(variationKey, 'variation');
52+
});
53+
});
54+
55+
describe('getFeatureEnabledFromVariation method', function() {
56+
it('should return false when variation is null', function() {
57+
var featureEnabled = decision.getFeatureEnabledFromVariation(rolloutDecisionObj);
58+
assert.strictEqual(featureEnabled, false);
59+
});
60+
61+
it('should return false when variation is not defined', function() {
62+
var featureEnabled = decision.getFeatureEnabledFromVariation({});
63+
assert.strictEqual(featureEnabled, false);
64+
});
65+
66+
it('should return featureEnabled boolean when variation is defined', function() {
67+
var featureEnabled = decision.getFeatureEnabledFromVariation(featureTestDecisionObj);
68+
assert.strictEqual(featureEnabled, true);
69+
});
70+
});
71+
})
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Copyright 2020, Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { DecisionObj } from '../decision_service';
18+
19+
/**
20+
* Get experiment key from the provided decision object
21+
* @param {DecisionObj} decisionObj Object representing decision
22+
* @returns {string} Experiment key or empty string if experiment is null
23+
*/
24+
export function getExperimentKey(decisionObj: DecisionObj): string {
25+
return decisionObj.experiment?.key ?? '';
26+
}
27+
28+
/**
29+
* Get variation key from the provided decision object
30+
* @param {DecisionObj} decisionObj Object representing decision
31+
* @returns {string} Variation key or empty string if variation is null
32+
*/
33+
export function getVariationKey(decisionObj: DecisionObj): string {
34+
return decisionObj.variation?.key ?? '';
35+
}
36+
37+
/**
38+
* Get featureEnabled from variation in the provided decision object
39+
* @param {DecisionObj} decisionObj Object representing decision
40+
* @returns {boolean} featureEnabled boolean or false if variation is null
41+
*/
42+
export function getFeatureEnabledFromVariation(decisionObj: DecisionObj): boolean {
43+
return decisionObj.variation?.featureEnabled ?? false;
44+
}

packages/optimizely-sdk/lib/core/decision_service/index.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export interface DecisionService {
5353
* @param {FeatureFlag} feature A feature flag object from project configuration
5454
* @param {string} userId A string identifying the user, for bucketing
5555
* @param {unknown} attributes Optional user attributes
56-
* @return {Decision} An object with experiment, variation, and decisionSource
56+
* @return {DecisionObj} An object with experiment, variation, and decisionSource
5757
* properties. If the user was not bucketed into a variation, the variation
5858
* property is null.
5959
*/
@@ -62,7 +62,7 @@ expor 2851 t interface DecisionService {
6262
feature: FeatureFlag,
6363
userId: string,
6464
attributes: unknown
65-
): Decision;
65+
): DecisionObj;
6666

6767
/**
6868
* Removes forced variation for given userId and experimentKey
@@ -99,7 +99,7 @@ interface Options {
9999
UNSTABLE_conditionEvaluators: unknown;
100100
}
101101

102-
interface Decision {
102+
export interface DecisionObj {
103103
experiment: Experiment | null;
104104
variation: Variation | null;
105105
decisionSource: string;

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -663,11 +663,11 @@ DecisionService.prototype.getForcedVariation = function(configObj, experimentKey
663663

664664
/**
665665
* Sets the forced variation for a user in a given experiment
666-
* @param {Object} configObj Object representing project configuration
667-
* @param {string} experimentKey Key for experiment.
668-
* @param {string} userId The user Id.
669-
* @param {string} variationKey Key for variation. If null, then clear the existing experiment-to-variation mapping
670-
* @return {boolean} A boolean value that indicates if the set completed successfully.
666+
* @param {Object} configObj Object representing project configuration
667+
* @param {string} experimentKey Key for experiment.
668+
* @param {string} userId The user Id.
669+
* @param {string|null} variationKey Key for variation. If null, then clear the existing experiment-to-variation mapping
670+
* @return {boolean} A boolean value that indicates if the set completed successfully.
671671
*/
672672
DecisionService.prototype.setForcedVariation = function(configObj, experimentKey, userId, variationKey) {
673673
if (variationKey != null && !stringValidator.validate(variationKey)) {

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
* limitations under the License.
1515
*/
1616
import { ProjectConfig } from '../project_config';
17+
import { DecisionObj } from '../decision_service';
1718
import { EventTags, UserAttributes } from '../../shared_types';
1819

1920
interface ImpressionConfig {
20-
experimentKey: string;
21-
variationKey: string;
21+
decisionObj: DecisionObj;
2222
userId: string;
23+
flagKey: string;
2324
userAttributes?: UserAttributes;
2425
clientEngine: string;
2526
clientVersion: string;
@@ -60,6 +61,10 @@ interface ImpressionEvent {
6061
id: string;
6162
key: string;
6263
} | null;
64+
65+
ruleKey: string,
66+
flagKey: string,
67+
ruleType: string,
6368
}
6469

6570
interface ConversionConfig {

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

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ import fns from '../../utils/fns';
1919
import projectConfig from '../project_config';
2020
import * as eventTagUtils from '../../utils/event_tag_utils';
2121
import * as attributesValidator from'../../utils/attributes_validator';
22+
import * as decision from '../decision';
2223

2324
var logger = getLogger('EVENT_BUILDER');
2425

2526
/**
2627
* Creates an ImpressionEvent object from decision data
2728
* @param {Object} config
28-
* @param {Object} config.configObj
29-
* @param {String} config.experimentKey
30-
* @param {String} config.variationKey
29+
* @param {Object} config.decisionObj
3130
* @param {String} config.userId
3231
* @param {Object} config.userAttributes
3332
* @param {String} config.clientEngine
@@ -36,17 +35,29 @@ var logger = getLogger('EVENT_BUILDER');
3635
*/
3736
export var buildImpressionEvent = function(config) {
3837
var configObj = config.configObj;
39-
var experimentKey = config.experimentKey;
40-
var variationKey = config.variationKey;
38+
var decisionObj = config.decisionObj;
4139
var userId = config.userId;
40+
var flagKey = config.flagKey;
4241
var userAttributes = config.userAttributes;
4342
var clientEngine = config.clientEngine;
4443
var clientVersion = config.clientVersion;
44+
var ruleType = decisionObj.decisionSource;
45+
var experimentKey = decision.getExperimentKey(decisionObj);
46+
var variationKey = decision.getVariationKey(decisionObj);
4547

46-
var variationId = projectConfig.getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey);
47-
var experimentId = projectConfig.getExperimentId(configObj, experimentKey);
48-
var layerId = projectConfig.getLayerId(configObj, experimentId);
48+
let experimentId = null;
49+
let variationId = null;
4950

51+
if (experimentKey !== '' && variationKey !== '') {
52+
variationId = projectConfig.getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey);
53+
}
54+
if (experimentKey !== '') {
55+
experimentId = projectConfig.getExperimentId(configObj, experimentKey);
56+
}
57+
let layerId = null;
58+
if (experimentId !== null) {
59+
layerId = projectConfig.getLayerId(configObj, experimentId);
60+
}
5061
return {
5162
type: 'impression',
5263
timestamp: fns.currentTimestamp(),
@@ -80,6 +91,10 @@ export var buildImpressionEvent = function(config) {
8091
id: variationId,
8192
key: variationKey,
8293
},
94+
95+
ruleKey: experimentKey,
96+
flagKey: flagKey,
97+
ruleType: ruleType,
8398
};
8499
};
85100

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

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,29 @@ describe('lib/event_builder/event_helpers', function() {
5656
describe('buildImpressionEvent', function() {
5757
describe('when botFiltering and anonymizeIP are true', function() {
5858
it('should build an ImpressionEvent with the correct attributes', function() {
59+
var decision = {
60+
experiment: {
61+
key: 'exp1',
62+
status: 'Running',
63+
forcedVariations: {},
64+
audienceIds: [],
65+
layerId: 'layer-id',
66+
trafficAllocation: [],
67+
variationKeyMap: {
68+
'variation': {
69+
key: 'var1',
70+
id: 'var1-id',
71+
}
72+
},
73+
id: 'exp1-id',
74+
variations: [{ key: 'var1', id: 'var1-id' }],
75+
},
76+
variation: {
77+
key: 'var1',
78+
id: 'var1-id',
79+
},
80+
decisionSource: 'experiment',
81+
}
5982
projectConfig.getVariationIdFromExperimentAndVariationKey
6083
.withArgs(configObj, 'exp1', 'var1')
6184
.returns('var1-id');
@@ -66,8 +89,8 @@ describe('lib/event_builder/event_helpers', function() {
6689

6790
var result = buildImpressionEvent({
6891
configObj: configObj,
69-
experimentKey: 'exp1',
70-
variationKey: 'var1',
92+
decisionObj: decision,
93+
flagKey: 'flagkey1',
7194
userId: 'user1',
7295
userAttributes: {
7396
plan_type: 'bronze',
@@ -113,12 +136,39 @@ describe('lib/event_builder/event_helpers', function() {
113136
id: 'var1-id',
114137
key: 'var1',
115138
},
139+
140+
ruleKey: "exp1",
141+
flagKey: 'flagkey1',
142+
ruleType: 'experiment',
116143
});
117144
});
118145
});
119146

120147
describe('when botFiltering and anonymizeIP are undefined', function() {
121148
it('should create an ImpressionEvent with the correct attributes', function() {
149+
var decision = {
150+
experiment: {
151+
key: 'exp1',
152+
status: 'Running',
153+
forcedVariations: {},
154+
audienceIds: [],
155+
layerId: '253442',
156+
trafficAllocation: [],
157+
variationKeyMap: {
158+
'variation': {
159+
key: 'var1',
160+
id: 'var1-id',
161+
}
162+
},
163+
id: '1237847778',
164+
variations: [{ key: 'var1', id: 'var1-id' }],
165+
},
166+
variation: {
167+
key: 'var1',
168+
id: 'var1-id',
169+
},
170+
decisionSource: 'experiment',
171+
}
122172
projectConfig.getVariationIdFromExperimentAndVariationKey
123173
.withArgs(configObj, 'exp1', 'var1')
124174
.returns('var1-id');
@@ -132,8 +182,8 @@ describe('lib/event_builder/event_helpers', function() {
132182

133183
var result = buildImpressionEvent({
134184
configObj: configObj,
135-
experimentKey: 'exp1',
136-
variationKey: 'var1',
185+
decisionObj: decision,
186+
flagKey: 'flagkey1',
137187
userId: 'user1',
138188
userAttributes: {
139189
plan_type: 'bronze',
@@ -179,6 +229,10 @@ describe('lib/event_builder/event_helpers', function() {
179229
id: 'var1-id',
180230
key: 'var1',
181231
},
232+
233+
ruleKey: "exp1",
234+
flagKey: 'flagkey1',
235+
ruleType: 'experiment',
182236
});
183237
});
184238
});

packages/optimizely-sdk/lib/core/event_builder/index.d.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ interface ImpressionOptions {
2222
clientEngine: string;
2323
clientVersion: string;
2424
configObj: ProjectConfig;
25-
experimentId: string;
25+
experimentId: string | null;
26+
ruleKey: string;
27+
flagKey: string;
28+
ruleType: string;
2629
eventKey?: string;
27-
variationId: string;
30+
variationId: string | null;
2831
logger?: LogHandler;
2932
userId: string;
3033
}

0 commit comments

Comments
 (0)
0