@@ -22,6 +22,9 @@ import {
22
22
VariationVariable ,
23
23
Variation ,
24
24
Rollout ,
25
+ OptimizelyAttribute ,
26
+ OptimizelyAudience ,
27
+ OptimizelyEvents
25
28
} from '../../shared_types' ;
26
29
27
30
interface FeatureVariablesMap {
@@ -38,15 +41,51 @@ export class OptimizelyConfig {
38
41
public featuresMap : OptimizelyFeaturesMap ;
39
42
public revision : string ;
40
43
public sdkKey ?: string ;
44
+ public attributes : OptimizelyAttribute [ ] ;
45
+ public audiences : OptimizelyAudience [ ] ;
46
+ public events : OptimizelyEvents [ ] ;
41
47
public environmentKey ?: string ;
42
48
private datafile : string ;
43
49
44
50
constructor ( configObj : ProjectConfig , datafile : string ) {
45
- this . experimentsMap = OptimizelyConfig . getExperimentsMap ( configObj ) ;
46
- this . featuresMap = OptimizelyConfig . getFeaturesMap ( configObj , this . experimentsMap ) ;
47
51
this . revision = configObj . revision ;
52
+ this . attributes = configObj . attributes ;
53
+ this . audiences = [ ] ;
54
+ this . events = configObj . events ;
48
55
this . datafile = datafile ;
56
+ const audiences = configObj . typedAudiences || [ ] ;
49
57
58
+ configObj . audiences . forEach ( ( oldAudience ) => {
59
+ if (
60
+ audiences . filter ( ( newAudience ) => {
61
+ newAudience == oldAudience ;
62
+ } ) . length == 0
63
+ ) {
64
+ if ( oldAudience . id != '$opt_dummy_audience' ) {
65
+ audiences . push ( oldAudience ) ;
66
+ }
67
+ }
68
+ } ) ;
69
+
70
+ this . audiences = audiences ;
71
+ const audienceMap : { [ key : string ] : string } = { } ;
72
+
73
+ for ( const audience of this . audiences ) {
74
+ audienceMap [ audience . id ] = audience . name ;
75
+ }
76
+ const updatedExperiments = OptimizelyConfig . getExperimentsMap ( configObj ) ;
77
+
78
+ Object . keys ( updatedExperiments ) . map ( function ( key ) {
79
+ const audiencesSerialized = serializeAudiences ( configObj , key , audienceMap ) ;
80
+ if ( audiencesSerialized ) {
81
+ updatedExperiments [ key ] . audiences = audiencesSerialized ;
82
+ }
83
+ } ) ;
84
+ const updatedRollouts = OptimizelyConfig . updateRollouts ( configObj , audienceMap ) ;
85
+
86
+ this . experimentsMap = updatedExperiments ;
87
+ this . featuresMap = OptimizelyConfig . getFeaturesMap ( configObj , updatedExperiments , updatedRollouts ) ;
88
+ console . log ( "FEATURES MAP:" , this . featuresMap )
50
89
if ( configObj . sdkKey && configObj . environmentKey ) {
51
90
this . sdkKey = configObj . sdkKey ;
52
91
this . environmentKey = configObj . environmentKey ;
@@ -69,57 +108,67 @@ export class OptimizelyConfig {
69
108
static getRolloutExperimentIds ( rollouts : Rollout [ ] ) : { [ key : string ] : boolean } {
70
109
return ( rollouts || [ ] ) . reduce ( ( experimentIds : { [ key : string ] : boolean } , rollout ) => {
71
110
rollout . experiments . forEach ( ( e ) => {
72
- ( experimentIds ) [ e . id ] = true ;
111
+ experimentIds [ e . id ] = true ;
73
112
} ) ;
74
113
75
114
return experimentIds ;
76
115
} , { } ) ;
77
116
}
78
117
118
+ /**
119
+ * Update rollouts by adding audiences keys in experiments
120
+ * @param {ProjectConfig } configObj
121
+ * @returns {audienceMap } Map of audiences
122
+ */
123
+ static updateRollouts ( configObj : ProjectConfig , audienceMap : { [ key : string ] : string } ) : Rollout [ ] {
124
+ return configObj . rollouts . map ( ( rollout ) => {
125
+ rollout . experiments = rollout . experiments . map ( ( experiment ) => {
126
+ const audiences = serializeAudiences ( configObj , experiment . key , audienceMap ) ;
127
+ if ( audiences ) {
128
+ experiment . audiences = audiences ;
129
+ }
130
+ return experiment ;
131
+ } ) ;
132
+ return rollout ;
133
+ } ) ;
134
+ }
135
+
79
136
/**
80
137
* Get Map of all experiments except rollouts
81
138
* @param {ProjectConfig } configObj
82
139
* @returns {OptimizelyExperimentsMap } Map of experiments excluding rollouts
83
140
*/
84
141
static getExperimentsMap ( configObj : ProjectConfig ) : OptimizelyExperimentsMap {
85
142
const rolloutExperimentIds = this . getRolloutExperimentIds ( configObj . rollouts ) ;
86
- const featureVariablesMap = ( configObj . featureFlags || [ ] ) . reduce (
87
- ( resultMap : FeatureVariablesMap , feature ) => {
88
- resultMap [ feature . id ] = feature . variables ;
89
- return resultMap ;
90
- } ,
91
- { } ,
92
- ) ;
93
-
94
- return ( configObj . experiments || [ ] ) . reduce (
95
- ( experiments : OptimizelyExperimentsMap , experiment ) => {
96
- // skip experiments that are part of a rollout
97
- if ( ! rolloutExperimentIds [ experiment . id ] ) {
98
- experiments [ experiment . key ] = {
99
- id : experiment . id ,
100
- key : experiment . key ,
101
- variationsMap : ( experiment . variations || [ ] ) . reduce (
102
- ( variations : { [ key : string ] : Variation } , variation ) => {
103
- variations [ variation . key ] = {
104
- id : variation . id ,
105
- key : variation . key ,
106
- variablesMap : this . getMergedVariablesMap ( configObj , variation , experiment . id , featureVariablesMap ) ,
107
- } ;
108
- if ( isFeatureExperiment ( configObj , experiment . id ) ) {
109
- variations [ variation . key ] . featureEnabled = variation . featureEnabled ;
110
- }
111
-
112
- return variations ;
113
- } ,
114
- { } ,
115
- ) ,
116
- } ;
117
- }
143
+ const featureVariablesMap = ( configObj . featureFlags || [ ] ) . reduce ( ( resultMap : FeatureVariablesMap , feature ) => {
144
+ resultMap [ feature . id ] = feature . variables ;
145
+ return resultMap ;
146
+ } , { } ) ;
147
+
148
+ return ( configObj . experiments || [ ] ) . reduce ( ( experiments : OptimizelyExperimentsMap , experiment ) => {
149
+ // skip experiments that are part of a rollout
150
+ if ( ! rolloutExperimentIds [ experiment . id ] ) {
151
+ experiments [ experiment . key ] = {
152
+ id : experiment . id ,
153
+ key : experiment . key ,
154
+ variationsMap : ( experiment . variations || [ ] ) . reduce ( ( variations : { [ key : string ] : Variation } , variation ) => {
155
+ variations [ variation . key ] = {
156
+ id : variation . id ,
157
+ key : variation . key ,
158
+ variablesMap : this . getMergedVariablesMap ( configObj , variation , experiment . id , featureVariablesMap ) ,
159
+ } ;
160
+ if ( isFeatureExperiment ( configObj , experiment . id ) ) {
161
+ variations [ variation . key ] . featureEnabled = variation . featureEnabled ;
162
+ }
118
163
119
- return experiments ;
120
- } ,
121
- { } ,
122
- )
164
+ return variations ;
165
+ } , { } ) ,
166
+ audiences : experiment . audiences ,
167
+ } ;
168
+ }
169
+
170
+ return experiments ;
171
+ } , { } ) ;
123
172
}
124
173
125
174
/**
@@ -134,7 +183,7 @@ export class OptimizelyConfig {
134
183
configObj : ProjectConfig ,
135
184
variation : Variation ,
136
185
experimentId : string ,
137
- featureVariablesMap : FeatureVariablesMap ,
186
+ featureVariablesMap : FeatureVariablesMap
138
187
) : OptimizelyVariablesMap {
139
188
const featureId = configObj . experimentFeatureMap [ experimentId ] ;
140
189
@@ -151,7 +200,7 @@ export class OptimizelyConfig {
151
200
152
201
return variablesMap ;
153
202
} ,
154
- { } ,
203
+ { }
155
204
) ;
156
205
variablesObject = ( experimentFeatureVariables || [ ] ) . reduce (
157
206
( variablesMap : OptimizelyVariablesMap , featureVariable ) => {
@@ -167,7 +216,7 @@ export class OptimizelyConfig {
167
216
168
217
return variablesMap ;
169
218
} ,
170
- { } ,
219
+ { }
171
220
) ;
172
221
}
173
222
@@ -182,40 +231,135 @@ export class OptimizelyConfig {
182
231
*/
183
232
static getFeaturesMap (
184
233
configObj : ProjectConfig ,
185
- allExperiments : OptimizelyExperimentsMap
234
+ allExperiments : OptimizelyExperimentsMap ,
235
+ rollouts : Rollout [ ]
186
236
) : OptimizelyFeaturesMap {
187
237
return ( configObj . featureFlags || [ ] ) . reduce ( ( features : OptimizelyFeaturesMap , feature ) => {
238
+ const filteredRollout = rollouts . filter ( ( rollout ) => {
239
+ console . log ( "Rollout:" , rollout . id , "Feature:" , feature . id )
240
+ return rollout . id == feature . id ;
241
+ } ) ;
242
+ console . log ( "FILTERED ROLLOUTS " , filteredRollout )
188
243
features [ feature . key ] = {
189
244
id : feature . id ,
190
245
key : feature . key ,
191
- experimentsMap : ( feature . experimentIds || [ ] ) . reduce (
192
- ( experiments : OptimizelyExperimentsMap , experimentId ) => {
193
- const experimentKey = configObj . experimentIdMap [ experimentId ] . key ;
194
- experiments [ experimentKey ] = allExperiments [ experimentKey ] ;
195
- return experiments ;
196
- } ,
197
- { } ,
198
- ) ,
199
- variablesMap : ( feature . variables || [ ] ) . reduce (
200
- ( variables : OptimizelyVariablesMap , variable ) => {
201
- variables [ variable . key ] = {
202
- id : variable . id ,
203
- key : variable . key ,
204
- type : variable . type ,
205
- value : variable . defaultValue ,
206
- } ;
246
+ experimentsMap : ( feature . experimentIds || [ ] ) . reduce ( ( experiments : OptimizelyExperimentsMap , experimentId ) => {
247
+ const experimentKey = configObj . experimentIdMap [ experimentId ] . key ;
248
+ experiments [ experimentKey ] = allExperiments [ experimentKey ] ;
249
+ return experiments ;
250
+ } , { } ) ,
251
+ variablesMap : ( feature . variables || [ ] ) . reduce ( ( variables : OptimizelyVariablesMap , variable ) => {
252
+ variables [ variable . key ] = {
253
+ id : variable . id ,
254
+ key : variable . key ,
255
+ type : variable . type ,
256
+ value : variable . defaultValue ,
257
+ } ;
207
258
208
- return variables ;
209
- } ,
210
- { } ,
211
- ) ,
259
+ return variables ;
260
+ } , { } ) ,
261
+ deliveryRules : Object . values ( allExperiments ) ,
262
+ experimentRules : filteredRollout
263
+ ? filteredRollout [ 0 ] . experiments . map ( ( experiment ) => {
264
+ return {
265
+ id : experiment . id ,
266
+ key : experiment . key ,
267
+ audiences : experiment . audiences ,
268
+ variationsMap : experiment . variationKeyMap ,
269
+ } ;
270
+ } )
271
+ : [ ] ,
212
272
} ;
213
273
214
274
return features ;
215
275
} , { } ) ;
216
276
}
217
277
}
218
278
279
+ /**
280
+ * Serialize audienceConditions
281
+ * @param {Array<string | string[]> } condition
282
+ * @returns {string } serialized audience condition
283
+ */
284
+ function serialized ( condition : Array < string | string [ ] > ) {
285
+ const operator = condition [ 0 ] ;
286
+ let first = '' ;
287
+ let second = '' ;
288
+ if ( condition [ 1 ] ) {
289
+ first = Array . isArray ( condition [ 1 ] ) ? `(${ serialized ( condition [ 1 ] ) } )` : `AUDIENCE(${ condition [ 1 ] } )` ;
290
+ }
291
+ if ( condition [ 2 ] ) {
292
+ second = Array . isArray ( condition [ 2 ] ) ? `(${ serialized ( condition [ 2 ] ) } )` : `AUDIENCE(${ condition [ 2 ] } )` ;
293
+ }
294
+ if ( condition [ 1 ] && condition [ 2 ] ) {
295
+ return `${ first } ${ operator . toString ( ) . toUpperCase ( ) } ${ second } ` ;
296
+ } else {
297
+ return `${ operator . toString ( ) . toUpperCase ( ) } ${ first } ` ;
298
+ }
299
+ }
300
+
301
+ /**
302
+ * replace audience ids with name
303
+ * @param {string } condition
304
+ * @param {{[key: string]: string} } audiences
305
+ * @returns {string } Updated serialized audienceCondition
306
+ */
307
+ function replaceAudienceIdsWithNames ( condition : string , audiences : { [ key : string ] : string } ) {
308
+ const beginWord = "AUDIENCE(" ;
309
+ const endWord = ")" ;
310
+ let keyIdx = 0 ;
311
+ let audienceId = "" ;
312
+ let collect = false ;
313
+
314
+ let replaced = "" ;
315
+ for ( const ch of condition ) {
316
+ if ( collect ) {
317
+ if ( ch == endWord ) {
318
+ replaced += `"${ audiences [ audienceId ] || audienceId } "` ;
319
+ collect = false ;
320
+ audienceId = "" ;
321
+ }
322
+ else {
323
+ audienceId += ch ;
324
+ }
325
+ continue ;
326
+ }
327
+
328
+ if ( ch == beginWord [ keyIdx ] ) {
329
+ keyIdx += 1 ;
330
+ if ( keyIdx == beginWord . length ) {
331
+ keyIdx = 0 ;
332
+ collect = true ;
333
+ }
334
+ continue ;
335
+ }
336
+ else {
337
+ if ( keyIdx > 0 ) {
338
+ replaced += beginWord . substring ( 0 , keyIdx ) ;
339
+ }
340
+ keyIdx = 0 ;
341
+ }
342
+
343
+ replaced += ch ;
344
+ }
345
+
346
+ return replaced ;
347
+ }
348
+
349
+ /**
350
+ * Return serialized audienceCondtion with replaced audienceIds with names
351
+ * @param {Array<string | string[]> } condition
352
+ * @returns {string } serialized audience condition
353
+ */
354
+ function serializeAudiences ( configObj : ProjectConfig , experimentKey : string , audienceMap : { [ key : string ] : string } ) {
355
+ const experiment = configObj . experimentKeyMap [ experimentKey ] ;
356
+ if ( experiment . audienceConditions ) {
357
+ const condition = serialized ( experiment . audienceConditions ) ;
358
+ return replaceAudienceIdsWithNames ( condition , audienceMap ) ;
359
+ }
360
+ return '' ;
361
+ }
362
+
219
363
/**
220
364
* Create an instance of OptimizelyConfig
221
365
* @param {ProjectConfig } configObj
0 commit comments