Skip to content

Commit dc47f71

Browse files
authored
feat: Implement IGNORE_USER_PROFILE_SERVICE flag in decide options (#642)
* * Check for sticky bucketing if decide options do not include shouldIgnoreUPS * Update getVariationForFeature method to include options * Fix existing unit test * Add unit test for IGNORE_USER_PROFILE_SERVICE flag in decide options * Fix comments * Clean up * Add unit test with IGNORE_USER_PROFILE_SERVICE in default decide options * Call __saveUserProfile only when shouldIgnoreUPS is true * Clean up * Add unit test when no stored bucketing exists
1 parent e6454b9 commit dc47f71

File tree

5 files changed

+208
-66
lines changed

5 files changed

+208
-66
lines changed

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

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,20 @@ export interface DecisionService {
3939

4040
/**
4141
* Gets a decision response containing variation where visitor will be bucketed and the decide reasons.
42-
* @param {ProjectConfig} configObj The parsed project configuration object
43-
* @param {string} experimentKey
44-
* @param {string} userId
45-
* @param {UserAttributes} attributes
46-
* @return {DecisionResponse} DecisionResponse DecisionResponse containing variation
47-
* the user is bucketed into and the decide reasons.
42+
* @param {ProjectConfig} configObj The parsed project configuration object
43+
* @param {string} experimentKey
44+
* @param {string} userId
45+
* @param {UserAttributes} attributes
46+
* @param {[key: string]: boolean} options Optional map of decide options
47+
* @return {DecisionResponse} DecisionResponse DecisionResponse containing variation
48+
* the user is bucketed into and the decide reasons.
4849
*/
4950
getVariation(
5051
configObj: ProjectConfig,
5152
experimentKey: string,
5253
userId: string,
53-
attributes?: UserAttributes
54+
attributes?: UserAttributes,
55+
options?: { [key: string]: boolean }
5456
): DecisionResponse<string | null>;
5557

5658
/**
@@ -60,19 +62,21 @@ export interface DecisionService {
6062
* experiment properties (both objects), as well as a decisionSource property.
6163
* decisionSource indicates whether the decision was due to a rollout or an
6264
* experiment.
63-
* @param {ProjectConfig} configObj The parsed project configuration object
64-
* @param {FeatureFlag} feature A feature flag object from project configuration
65-
* @param {string} userId A string identifying the user, for bucketing
66-
* @param {unknown} attributes Optional user attributes
67-
* @return {DecisionResponse} DecisionResponse DecisionResponse containing an object with experiment, variation, and decisionSource
68-
* properties and the decide reasons. If the user was not bucketed into a variation, the
69-
* variation property in decision object is null.
65+
* @param {ProjectConfig} configObj The parsed project configuration object
66+
* @param {FeatureFlag} feature A feature flag object from project configuration
67+
* @param {string} userId A string identifying the user, for bucketing
68+
* @param {unknown} attributes Optional user attributes
69+
* @param {[key: string]: boolean} options Optional map of decide options
70+
* @return {DecisionResponse} DecisionResponse DecisionResponse containing an object with experiment, variation, and decisionSource
71+
* properties and the decide reasons. If the user was not bucketed into a variation, the
72+
* variation property in decision object is null.
7073
*/
7174
getVariationForFeature(
7275
configObj: ProjectConfig,
7376
feature: FeatureFlag,
7477
userId: string,
75-
attributes: unknown
78+
attributes: unknown,
79+
options?: { [key: string]: boolean }
7680
): DecisionResponse<DecisionObj>;
7781

7882
/**

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

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import * as enums from '../../utils/enums';
2121
import projectConfig from '../project_config';
2222
import AudienceEvaluator from '../audience_evaluator';
2323
import * as stringValidator from '../../utils/string_value_validator';
24+
import { OptimizelyDecideOptions } from '../../shared_types';
2425

2526
var MODULE_NAME = 'DECISION_SERVICE';
2627
var ERROR_MESSAGES = enums.ERROR_MESSAGES;
@@ -55,14 +56,15 @@ function DecisionService(options) {
5556

5657
/**
5758
* Gets variation where visitor will be bucketed.
58-
* @param {Object} configObj The parsed project configuration object
59-
* @param {string} experimentKey
60-
* @param {string} userId
61-
* @param {Object} attributes
62-
* @return {Object} DecisionResonse DecisionResonse containing the variation the user is bucketed into
63-
* and the decide reasons.
59+
* @param {Object} configObj The parsed project configuration object
60+
* @param {string} experimentKey
61+
* @param {string} userId
62+
* @param {Object} attributes
63+
* @param {[key: string]: boolean} options Optional map of decide options
64+
* @return {Object} DecisionResonse DecisionResonse containing the variation the user is bucketed into
65+
* and the decide reasons.
6466
*/
65-
DecisionService.prototype.getVariation = function(configObj, experimentKey, userId, attributes) {
67+
DecisionService.prototype.getVariation = function(configObj, experimentKey, userId, attributes, options = {}) {
6668
// by default, the bucketing ID should be the user ID
6769
var bucketingId = this._getBucketingId(userId, attributes);
6870
var decideReasons = [];
@@ -98,26 +100,30 @@ DecisionService.prototype.getVariation = function(configObj, experimentKey, user
98100
};
99101
}
100102

101-
// check for sticky bucketing
102-
var experimentBucketMap = this.__resolveExperimentBucketMap(userId, attributes);
103-
variation = this.__getStoredVariation(configObj, experiment, userId, experimentBucketMap);
104-
if (variation) {
105-
var returningStoredVariationMessage = sprintf(
106-
LOG_MESSAGES.RETURNING_STORED_VARIATION,
107-
MODULE_NAME,
108-
variation.key,
109-
experimentKey,
110-
userId
111-
);
112-
this.logger.log(
113-
LOG_LEVEL.INFO,
114-
returningStoredVariationMessage
115-
);
116-
decideReasons.push(returningStoredVariationMessage);
117-
return {
118-
result: variation.key,
119-
reasons: decideReasons,
120-
};
103+
var shouldIgnoreUPS = options[OptimizelyDecideOptions.IGNORE_USER_PROFILE_SERVICE];
104+
105+
// check for sticky bucketing if decide options do not include shouldIgnoreUPS
106+
if (!shouldIgnoreUPS) {
107+
var experimentBucketMap = this.__resolveExperimentBucketMap(userId, attributes);
108+
variation = this.__getStoredVariation(configObj, experiment, userId, experimentBucketMap);
109+
if (variation) {
110+
var returningStoredVariationMessage = sprintf(
111+
LOG_MESSAGES.RETURNING_STORED_VARIATION,
112+
MODULE_NAME,
113+
variation.key,
114+
experimentKey,
115+
userId
116+
);
117+
this.logger.log(
118+
LOG_LEVEL.INFO,
119+
returningStoredVariationMessage
120+
);
121+
decideReasons.push(returningStoredVariationMessage);
122+
return {
123+
result: variation.key,
124+
reasons: decideReasons,
125+
};
126+
}
121127
}
122128

123129
// Perform regular targeting and bucketing
@@ -174,8 +180,10 @@ DecisionService.prototype.getVariation = function(configObj, experimentKey, user
174180
);
175181
this.logger.log(LOG_LEVEL.INFO, userInVariationLogMessage);
176182
decideReasons.push(userInVariationLogMessage);
177-
// persist bucketing
178-
this.__saveUserProfile(experiment, variation, userId, experimentBucketMap);
183+
// persist bucketing if decide options do not include shouldIgnoreUPS
184+
if (!shouldIgnoreUPS) {
185+
this.__saveUserProfile(experiment, variation, userId, experimentBucketMap);
186+
}
179187

180188
return {
181189
result: variation.key,
@@ -414,17 +422,18 @@ DecisionService.prototype.__saveUserProfile = function(experiment, variation, us
414422
* experiment properties (both objects), as well as a decisionSource property.
415423
* decisionSource indicates whether the decision was due to a rollout or an
416424
* experiment.
417-
* @param {Object} configObj The parsed project configuration object
418-
* @param {Object} feature A feature flag object from project configuration
419-
* @param {String} userId A string identifying the user, for bucketing
420-
* @param {Object} attributes Optional user attributes
421-
* @return {Object} DecisionResponse DecisionResponse containing an object with experiment, variation, and decisionSource
422-
* properties and decide reasons. If the user was not bucketed into a variation, the variation
423-
* property in decision object is null.
425+
* @param {Object} configObj The parsed project configuration object
426+
* @param {Object} feature A feature flag object from project configuration
427+
* @param {String} userId A string identifying the user, for bucketing
428+
* @param {Object} attributes Optional user attributes
429+
* @param {[key: string]: boolean} options Map of decide options
430+
* @return {Object} DecisionResponse DecisionResponse containing an object with experiment, variation, and decisionSource
431+
* properties and decide reasons. If the user was not bucketed into a variation, the variation
432+
* property in decision object is null.
424433
*/
425-
DecisionService.prototype.getVariationForFeature = function(configObj, feature, userId, attributes) {
434+
DecisionService.prototype.getVariationForFeature = function(configObj, feature, userId, attributes, options = {}) {
426435
var decideReasons = [];
427-
var decisionVariation = this._getVariationForFeatureExperiment(configObj, feature, userId, attributes);
436+
var decisionVariation = this._getVariationForFeatureExperiment(configObj, feature, userId, attributes, options);
428437
decideReasons.push(...decisionVariation.reasons);
429438
var experimentDecision = decisionVariation.result;
430439

@@ -457,7 +466,8 @@ DecisionService.prototype.getVariationForFeature = function(configObj, feature,
457466
};
458467
};
459468

460-
DecisionService.prototype._getVariationForFeatureExperiment = function(configObj, feature, userId, attributes) {
469+
470+
DecisionService.prototype._getVariationForFeatureExperiment = function(configObj, feature, userId, attributes, options = {}) {
461471
var decideReasons = [];
462472
var experiment = null;
463473
var variationKey = null;
@@ -468,7 +478,7 @@ DecisionService.prototype._getVariationForFeatureExperiment = function(configObj
468478
if (group) {
469479
experiment = this._getExperimentInGroup(configObj, group, userId);
470480
if (experiment && feature.experimentIds.indexOf(experiment.id) !== -1) {
471-
decisionVariation = this.getVariation(configObj, experiment.key, userId, attributes);
481+
decisionVariation = this.getVariation(configObj, experiment.key, userId, attributes, options);
472482
decideReasons.push(...decisionVariation.reasons);
473483
variationKey = decisionVariation.result;
474484
}
@@ -478,7 +488,7 @@ DecisionService.prototype._getVariationForFeatureExperiment = function(configObj
478488
// with one experiment, so we look at the first experiment ID only
479489
experiment = projectConfig.getExperimentFromId(configObj, feature.experimentIds[0], this.logger);
480490
if (experiment) {
481-
decisionVariation = this.getVariation(configObj, experiment.key, userId, attributes);
491+
decisionVariation = this.getVariation(configObj, experiment.key, userId, attributes, options);
482492
decideReasons.push(...decisionVariation.reasons);
483493
variationKey = decisionVariation.result;
484494
}

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ describe('lib/core/decision_service', function() {
155155
var userProfileLookupStub;
156156
var userProfileSaveStub;
157157
var fakeDecisionWhitelistedVariation = {
158-
getResult: sinon.stub().returns(null),
159-
getReasons: sinon.stub().returns([])
158+
result: null,
159+
reasons: [],
160160
}
161161
beforeEach(function() {
162162
userProfileServiceInstance = {
@@ -1364,9 +1364,15 @@ describe('lib/core/decision_service', function() {
13641364
decisionSource: DECISION_SOURCES.FEATURE_TEST,
13651365
};
13661366
assert.deepEqual(decision, expectedDecision);
1367-
sinon.assert.calledWithExactly(getVariationStub, configObj, 'testing_my_feature', 'user1', {
1368-
test_attribute: 'test_value',
1369-
});
1367+
sinon.assert.calledWithExactly(
1368+
getVariationStub,
1369+
configObj,
1370+
'testing_my_feature', 'user1',
1371+
{
1372+
test_attribute: 'test_value',
1373+
},
1374+
{}
1375+
);
13701376
});
13711377
});
13721378

0 commit comments

Comments
 (0)