@@ -36,7 +36,6 @@ type ExperimentCacheType = {[key in ToggleIdType]: ExperimentToggleValueType}
36
36
type CacheType = FeatureEnabledCacheType | ExperimentCacheType
37
37
type ForcedTogglesType = { [ Tkey in ToggleIdType ] : ToggleValueType }
38
38
39
- let featureEnabledCache : FeatureEnabledCacheType = { }
40
39
let experimentCache : ExperimentCacheType = { }
41
40
const forcedToggles : ForcedTogglesType = { }
42
41
@@ -54,7 +53,6 @@ export const registerLibrary = (lib) => {
54
53
optimizely = lib
55
54
}
56
55
57
- const clearFeatureEnabledCache = ( ) => ( featureEnabledCache = { } )
58
56
const clearExperimentCache = ( ) => ( experimentCache = { } )
59
57
60
58
/**
@@ -77,7 +75,6 @@ export const forceToggles = (toggleKeyValues: {
77
75
}
78
76
79
77
const invalidateCaches = ( ) => {
80
- clearFeatureEnabledCache ( )
81
78
clearExperimentCache ( )
82
79
}
83
80
@@ -134,24 +131,13 @@ export enum ExperimentType {
134
131
*
135
132
* It would be best if Opticks abstracts this difference from the client in future versions.
136
133
*/
137
- interface ActivateMVTNotificationPayload extends ListenerPayload {
134
+ interface ActivateNotificationPayload extends ListenerPayload {
138
135
type : ExperimentType . mvt
139
136
decisionInfo : {
140
137
experimentKey : ToggleIdType
141
138
variationKey : VariantType
142
139
}
143
140
}
144
- interface ActivateFlagNotificationPayload extends ListenerPayload {
145
- type : ExperimentType . flag
146
- decisionInfo : {
147
- flagKey : ToggleIdType
148
- enabled : boolean
149
- }
150
- }
151
-
152
- export type ActivateNotificationPayload =
153
- | ActivateMVTNotificationPayload
154
- | ActivateFlagNotificationPayload
155
141
156
142
/**
157
143
* Initializes Opticks with the supplied Optimizely datafile,
@@ -185,11 +171,32 @@ export const initialize = (
185
171
*/
186
172
export const addActivateListener = (
187
173
listener : NotificationListener < ActivateNotificationPayload >
188
- ) =>
189
- optimizelyClient . notificationCenter . addNotificationListener (
174
+ ) => {
175
+ const decisionListener = ( payload : ActivateNotificationPayload ) => {
176
+ const { variationKey} = payload . decisionInfo
177
+ const decision =
178
+ variationKey === 'on' ? 'b' : variationKey === 'off' ? 'a' : variationKey
179
+
180
+ const updatedPayload = {
181
+ ...payload ,
182
+ decisionInfo : { ...payload . decisionInfo , variationKey : decision }
183
+ }
184
+
185
+ return listener ( updatedPayload )
186
+ }
187
+
188
+ /**
189
+ * This is a temporary support for the initial convention defined during the migration that "on" === "b" and "off" === "a"
190
+ * With the latest SDK version, A/B tests and target deliveries return a string key for the variation
191
+ * We will migrate the current experiments to the new convention and remove this temporary support.
192
+ * In the new convention we will always use the variation key as the decision.
193
+ */
194
+ // TODO (@vlacerda) [2024-06-30]: By this time we should ping @vlacerda to evaluate again if the fix is still needed and remove it if not.
195
+ return optimizelyClient . notificationCenter . addNotificationListener (
190
196
NOTIFICATION_TYPES . DECISION ,
191
- listener
197
+ decisionListener
192
198
)
199
+ }
193
200
194
201
const isForcedOrCached = ( toggleId : ToggleIdType , cache : CacheType ) : boolean =>
195
202
forcedToggles . hasOwnProperty ( toggleId ) || cache . hasOwnProperty ( toggleId )
@@ -209,27 +216,6 @@ const validateUserId = (id) => {
209
216
if ( ! id ) throw new Error ( 'Opticks: Fatal error: user id is not set' )
210
217
}
211
218
212
- const getToggleDecisionStatus = (
213
- toggleId : ToggleIdType
214
- ) : ExperimentToggleValueType => {
215
- validateUserId ( userId )
216
-
217
- const DEFAULT = false
218
-
219
- if ( isForcedOrCached ( toggleId , featureEnabledCache ) ) {
220
- const value = getForcedOrCached ( toggleId , featureEnabledCache )
221
- return typeof value === 'boolean' ? value : DEFAULT
222
- }
223
-
224
- userContext = optimizelyClient . createUserContext (
225
- userId ,
226
- audienceSegmentationAttributes
227
- )
228
- const decision = userContext . decide ( toggleId )
229
-
230
- return ( featureEnabledCache [ toggleId ] = decision . enabled )
231
- }
232
-
233
219
/**
234
220
* Determines whether a user satisfies the audience requirements for a toggle.
235
221
@@ -241,34 +227,43 @@ const getToggleDecisionStatus = (
241
227
export const isUserInRolloutAudience = ( toggleId : ToggleIdType ) => {
242
228
// @ts -expect-error we're being naughty here and using internals
243
229
const config = optimizelyClient . projectConfigManager . getConfig ( )
230
+ // feature in the config object represents an a/b test
244
231
const feature = config . featureKeyMap [ toggleId ]
232
+ // rollout is a targeted delivery
245
233
const rollout = config . rolloutIdMap [ feature . rolloutId ]
234
+ // both a/b tests and targeted deliveries can have audiences
235
+ const allRules = [ ...rollout . experiments ]
236
+
237
+ /**
238
+ * The feature object supplies ids through experimentIds.
239
+ * We find the rules for each experiment and add them to the allRules array.
240
+ */
241
+ if ( feature . experimentIds . length > 0 ) {
242
+ const { experimentIds} = feature
243
+ const experimentRules = experimentIds . map (
244
+ ( experimentId ) => config . experimentIdMap [ experimentId ]
245
+ )
246
+ allRules . push ( ...experimentRules )
247
+ }
246
248
247
- const endIndex = rollout . experiments . length - 1
248
- let index : number
249
- let isInAnyAudience = false
250
-
251
- for ( index = 0 ; index <= endIndex ; index ++ ) {
252
- const rolloutRule = rollout . experiments [ index ]
253
-
249
+ const isInAnyAudience = allRules . reduce ( ( acc , rule ) => {
254
250
// Reference: https://github.com/optimizely/javascript-sdk/blob/851b06622fa6a0239500b3b65e2d3937334960de/lib/core/decision_service/index.ts#L403
255
251
const decisionIfUserIsInAudience =
256
252
// @ts -expect-error we're being naughty here and using internals
257
253
optimizelyClient . decisionService . checkIfUserIsInAudience (
258
254
config ,
259
- rolloutRule ,
255
+ rule ,
260
256
'rule' ,
261
257
userContext ,
262
258
audienceSegmentationAttributes ,
263
259
''
264
260
)
265
261
266
- if (
267
- decisionIfUserIsInAudience . result &&
268
- ! isPausedBooleanToggle ( rolloutRule )
269
- )
270
- isInAnyAudience = true
271
- }
262
+ if ( decisionIfUserIsInAudience . result && ! isPausedBooleanToggle ( rule ) )
263
+ return true
264
+
265
+ return acc
266
+ } , false )
272
267
273
268
return isInAnyAudience
274
269
}
@@ -302,19 +297,26 @@ const getToggle = (toggleId: ToggleIdType): ExperimentToggleValueType => {
302
297
return typeof value === 'string' ? value : DEFAULT
303
298
}
304
299
300
+ userContext = optimizelyClient . createUserContext (
301
+ userId ,
302
+ audienceSegmentationAttributes
303
+ )
304
+
305
+ const variationKey = userContext . decide ( toggleId ) . variationKey
306
+
307
+ /**
308
+ * This is a temporary support for the initial convention defined during the migration that "on" === "b" and "off" === "a"
309
+ * With the latest SDK version, A/B tests and target deliveries return a string key for the variation
310
+ * We will migrate the current experiments to the new convention and remove this temporary support.
311
+ * In the new convention we will always use the variation key as the decision.
312
+ */
313
+ // TODO (@vlacerda) [2024-06-30]: By this time we should ping @vlacerda to evaluate again if the fix is still needed and remove it if not.
314
+ const decision =
315
+ variationKey === 'on' ? 'b' : variationKey === 'off' ? 'a' : variationKey
316
+
305
317
// Assuming the variation keys follow a, b, c, etc. convention
306
318
// TODO: Enforce ^ ?
307
- return ( experimentCache [ toggleId ] =
308
- optimizelyClient . activate (
309
- toggleId ,
310
- userId ,
311
- audienceSegmentationAttributes
312
- ) || DEFAULT )
313
- }
314
-
315
- const convertBooleanToggleToFeatureVariant = ( toggleId : ToggleIdType ) => {
316
- const isFeatureEnabled = getToggleDecisionStatus ( toggleId )
317
- return isFeatureEnabled ? 'b' : 'a'
319
+ return ( experimentCache [ toggleId ] = decision || DEFAULT )
318
320
}
319
321
320
322
/**
@@ -335,15 +337,7 @@ export function toggle<A extends any[]>(
335
337
...variants : A
336
338
) : ToggleFuncReturnType < A >
337
339
export function toggle ( toggleId : ToggleIdType , ...variants ) {
338
- // An A/B/C... test
339
- if ( variants . length > 2 ) {
340
- return baseToggle ( getToggle ) ( toggleId , ...variants )
341
- } else {
342
- return baseToggle ( convertBooleanToggleToFeatureVariant ) (
343
- toggleId ,
344
- ...variants
345
- )
346
- }
340
+ return baseToggle ( getToggle ) ( toggleId , ...variants )
347
341
}
348
342
349
343
/**
0 commit comments