1919 */
2020import { sprintf } from '@optimizely/js-sdk-utils' ;
2121import murmurhash from 'murmurhash' ;
22+ import { LogHandler } from '@optimizely/js-sdk-logging' ;
23+ import {
24+ DecisionResponse ,
25+ Experiment ,
26+ Variation
27+ } from '../../shared_types' ;
2228
2329import {
2430 ERROR_MESSAGES ,
2531 LOG_LEVEL ,
2632 LOG_MESSAGES ,
2733} from '../../utils/enums' ;
2834
29- var HASH_SEED = 1 ;
30- var MAX_HASH_VALUE = Math . pow ( 2 , 32 ) ;
31- var MAX_TRAFFIC_VALUE = 10000 ;
32- var MODULE_NAME = 'BUCKETER' ;
33- var RANDOM_POLICY = 'random' ;
35+ const HASH_SEED = 1 ;
36+ const MAX_HASH_VALUE = Math . pow ( 2 , 32 ) ;
37+ const MAX_TRAFFIC_VALUE = 10000 ;
38+ const MODULE_NAME = 'BUCKETER' ;
39+ const RANDOM_POLICY = 'random' ;
40+
41+ interface TrafficAllocation {
42+ entityId : string ;
43+ endOfRange : number ;
44+ }
45+
46+ interface Group {
47+ id : string ;
48+ policy : string ;
49+ trafficAllocation : TrafficAllocation [ ] ;
50+ }
51+
52+ interface BucketerParams {
53+ experimentId : string ;
54+ experimentKey : string ;
55+ userId : string ;
56+ trafficAllocationConfig : TrafficAllocation [ ] ;
57+ experimentKeyMap : { [ key : string ] : Experiment } ;
58+ groupIdMap : { [ key : string ] : Group } ;
59+ variationIdMap : { [ id : string ] : Variation } ;
60+ varationIdMapKey : string ;
61+ logger : LogHandler ;
62+ bucketingId : string ;
63+ }
3464
3565/**
3666 * Determines ID of variation to be shown for the given input params
@@ -48,18 +78,18 @@ var RANDOM_POLICY = 'random';
4878 * @return {Object } DecisionResponse DecisionResponse containing variation ID that user has been bucketed into,
4979 * null if user is not bucketed into any experiment and the decide reasons.
5080 */
51- export var bucket = function ( bucketerParams ) {
52- var decideReasons = [ ] ;
81+ export const bucket = function ( bucketerParams : BucketerParams ) : DecisionResponse < string | null > {
82+ const decideReasons : string [ ] = [ ] ;
5383 // Check if user is in a random group; if so, check if user is bucketed into a specific experiment
54- var experiment = bucketerParams . experimentKeyMap [ bucketerParams . experimentKey ] ;
55- var groupId = experiment [ 'groupId' ] ;
84+ const experiment = bucketerParams . experimentKeyMap [ bucketerParams . experimentKey ] ;
85+ const groupId = experiment [ 'groupId' ] ;
5686 if ( groupId ) {
57- var group = bucketerParams . groupIdMap [ groupId ] ;
87+ const group = bucketerParams . groupIdMap [ groupId ] ;
5888 if ( ! group ) {
5989 throw new Error ( sprintf ( ERROR_MESSAGES . INVALID_GROUP_ID , MODULE_NAME , groupId ) ) ;
6090 }
6191 if ( group . policy === RANDOM_POLICY ) {
62- var bucketedExperimentId = this . bucketUserIntoExperiment (
92+ const bucketedExperimentId = bucketUserIntoExperiment (
6393 group ,
6494 bucketerParams . bucketingId ,
6595 bucketerParams . userId ,
@@ -68,7 +98,7 @@ export var bucket = function(bucketerParams) {
6898
6999 // Return if user is not bucketed into any experiment
70100 if ( bucketedExperimentId === null ) {
71- var notbucketedInAnyExperimentLogMessage = sprintf (
101+ const notbucketedInAnyExperimentLogMessage = sprintf (
72102 LOG_MESSAGES . USER_NOT_IN_ANY_EXPERIMENT ,
73103 MODULE_NAME ,
74104 bucketerParams . userId ,
@@ -84,7 +114,7 @@ export var bucket = function(bucketerParams) {
84114
85115 // Return if user is bucketed into a different experiment than the one specified
86116 if ( bucketedExperimentId !== bucketerParams . experimentId ) {
87- var notBucketedIntoExperimentOfGroupLogMessage = sprintf (
117+ const notBucketedIntoExperimentOfGroupLogMessage = sprintf (
88118 LOG_MESSAGES . USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP ,
89119 MODULE_NAME ,
90120 bucketerParams . userId ,
@@ -100,7 +130,7 @@ export var bucket = function(bucketerParams) {
100130 }
101131
102132 // Continue bucketing if user is bucketed into specified experiment
103- var bucketedIntoExperimentOfGroupLogMessage = sprintf (
133+ const bucketedIntoExperimentOfGroupLogMessage = sprintf (
104134 LOG_MESSAGES . USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP ,
105135 MODULE_NAME ,
106136 bucketerParams . userId ,
@@ -111,10 +141,10 @@ export var bucket = function(bucketerParams) {
111141 decideReasons . push ( bucketedIntoExperimentOfGroupLogMessage ) ;
112142 }
113143 }
114- var bucketingId = sprintf ( '%s%s' , bucketerParams . bucketingId , bucketerParams . experimentId ) ;
115- var bucketValue = this . _generateBucketValue ( bucketingId ) ;
144+ const bucketingId = sprintf ( '%s%s' , bucketerParams . bucketingId , bucketerParams . experimentId ) ;
145+ const bucketValue = _generateBucketValue ( bucketingId ) ;
116146
117- var bucketedUserLogMessage = sprintf (
147+ const bucketedUserLogMessage = sprintf (
118148 LOG_MESSAGES . USER_ASSIGNED_TO_EXPERIMENT_BUCKET ,
119149 MODULE_NAME ,
120150 bucketValue ,
@@ -123,18 +153,19 @@ export var bucket = function(bucketerParams) {
123153 bucketerParams . logger . log ( LOG_LEVEL . DEBUG , bucketedUserLogMessage ) ;
124154 decideReasons . push ( bucketedUserLogMessage ) ;
125155
126- var entityId = this . _findBucket ( bucketValue , bucketerParams . trafficAllocationConfig ) ;
127-
128- if ( ! bucketerParams . variationIdMap . hasOwnProperty ( entityId ) ) {
129- if ( entityId ) {
130- var invalidVariationIdLogMessage = sprintf ( LOG_MESSAGES . INVALID_VARIATION_ID , MODULE_NAME ) ;
131- bucketerParams . logger . log ( LOG_LEVEL . WARNING , invalidVariationIdLogMessage ) ;
132- decideReasons . push ( invalidVariationIdLogMessage ) ;
156+ const entityId = _findBucket ( bucketValue , bucketerParams . trafficAllocationConfig ) ;
157+ if ( entityId !== null ) {
158+ if ( ! bucketerParams . variationIdMap [ entityId ] ) {
159+ if ( entityId ) {
160+ const invalidVariationIdLogMessage = sprintf ( LOG_MESSAGES . INVALID_VARIATION_ID , MODULE_NAME ) ;
161+ bucketerParams . logger . log ( LOG_LEVEL . WARNING , invalidVariationIdLogMessage ) ;
162+ decideReasons . push ( invalidVariationIdLogMessage ) ;
163+ }
164+ return {
165+ result : null ,
166+ reasons : decideReasons ,
167+ } ;
133168 }
134- return {
135- result : null ,
136- reasons : decideReasons ,
137- } ;
138169 }
139170
140171 return {
@@ -145,54 +176,63 @@ export var bucket = function(bucketerParams) {
145176
146177/**
147178 * Returns bucketed experiment ID to compare against experiment user is being called into
148- * @param { Object } group Group that experiment is in
149- * @param {string } bucketingId Bucketing ID
150- * @param {string } userId ID of user to be bucketed into experiment
151- * @param { Object } logger Logger implementation
152- * @return {string|null } ID of experiment if user is bucketed into experiment within the group, null otherwise
179+ * @param { Group } group Group that experiment is in
180+ * @param {string } bucketingId Bucketing ID
181+ * @param {string } userId ID of user to be bucketed into experiment
182+ * @param { LogHandler } logger Logger implementation
183+ * @return {string|null } ID of experiment if user is bucketed into experiment within the group, null otherwise
153184 */
154- export var bucketUserIntoExperiment = function ( group , bucketingId , userId , logger ) {
155- var bucketingKey = sprintf ( '%s%s' , bucketingId , group . id ) ;
156- var bucketValue = this . _generateBucketValue ( bucketingKey ) ;
185+ export const bucketUserIntoExperiment = function (
186+ group : Group ,
187+ bucketingId : string ,
188+ userId : string ,
189+ logger : LogHandler
190+ ) : string | null {
191+ const bucketingKey = sprintf ( '%s%s' , bucketingId , group . id ) ;
192+ const bucketValue = _generateBucketValue ( bucketingKey ) ;
157193 logger . log (
158194 LOG_LEVEL . DEBUG ,
159195 sprintf ( LOG_MESSAGES . USER_ASSIGNED_TO_EXPERIMENT_BUCKET , MODULE_NAME , bucketValue , userId )
160196 ) ;
161- var trafficAllocationConfig = group . trafficAllocation ;
162- var bucketedExperimentId = this . _findBucket ( bucketValue , trafficAllocationConfig ) ;
197+ const trafficAllocationConfig = group . trafficAllocation ;
198+ const bucketedExperimentId = _findBucket ( bucketValue , trafficAllocationConfig ) ;
163199 return bucketedExperimentId ;
164200} ;
165201
166202/**
167203 * Returns entity ID associated with bucket value
168- * @param {string } bucketValue
169- * @param {Object [] } trafficAllocationConfig
170- * @param {number } trafficAllocationConfig[].endOfRange
171- * @param {number } trafficAllocationConfig[].entityId
172- * @return {string|null } Entity ID for bucketing if bucket value is within traffic allocation boundaries, null otherwise
204+ * @param {number } bucketValue
205+ * @param {TrafficAllocation [] } trafficAllocationConfig
206+ * @param {number } trafficAllocationConfig[].endOfRange
207+ * @param {string } trafficAllocationConfig[].entityId
208+ * @return {string|null } Entity ID for bucketing if bucket value is within traffic allocation boundaries, null otherwise
173209 */
174- export var _findBucket = function ( bucketValue , trafficAllocationConfig ) {
175- for ( var i = 0 ; i < trafficAllocationConfig . length ; i ++ ) {
210+ export const _findBucket = function (
211+ bucketValue : number ,
212+ trafficAllocationConfig : TrafficAllocation [ ]
213+ ) : string | null {
214+ for ( let i = 0 ; i < trafficAllocationConfig . length ; i ++ ) {
176215 if ( bucketValue < trafficAllocationConfig [ i ] . endOfRange ) {
177216 return trafficAllocationConfig [ i ] . entityId ;
178217 }
179218 }
219+
180220 return null ;
181221} ;
182222
183223/**
184224 * Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE)
185- * @param {string } bucketingKey String value for bucketing
186- * @return {string } the generated bucket value
187- * @throws If bucketing value is not a valid string
225+ * @param {string } bucketingKey String value for bucketing
226+ * @return {number } The generated bucket value
227+ * @throws If bucketing value is not a valid string
188228 */
189- export var _generateBucketValue = function ( bucketingKey ) {
229+ export const _generateBucketValue = function ( bucketingKey : string ) : number {
190230 try {
191231 // NOTE: the mmh library already does cast the hash value as an unsigned 32bit int
192232 // https://github.com/perezd/node-murmurhash/blob/master/murmurhash.js#L115
193- var hashValue = murmurhash . v3 ( bucketingKey , HASH_SEED ) ;
194- var ratio = hashValue / MAX_HASH_VALUE ;
195- return parseInt ( ratio * MAX_TRAFFIC_VALUE , 10 ) ;
233+ const hashValue = murmurhash . v3 ( bucketingKey , HASH_SEED ) ;
234+ const ratio = hashValue / MAX_HASH_VALUE ;
235+ return Math . floor ( ratio * MAX_TRAFFIC_VALUE ) ;
196236 } catch ( ex ) {
197237 throw new Error ( sprintf ( ERROR_MESSAGES . INVALID_BUCKETING_ID , MODULE_NAME , bucketingKey , ex . message ) ) ;
198238 }
@@ -201,6 +241,4 @@ export var _generateBucketValue = function(bucketingKey) {
201241export default {
202242 bucket : bucket ,
203243 bucketUserIntoExperiment : bucketUserIntoExperiment ,
204- _findBucket : _findBucket ,
205- _generateBucketValue : _generateBucketValue ,
206244} ;
0 commit comments