@@ -176,71 +176,42 @@ func (o *OptimizelyClient) decide(userContext *OptimizelyUserContext, key string
176
176
QualifiedSegments : userContext .GetQualifiedSegments (),
177
177
}
178
178
179
- var variationKey string
180
- var eventSent , flagEnabled bool
181
179
allOptions := o .getAllOptions (options )
182
180
decisionReasons := decide .NewDecisionReasons (& allOptions )
183
181
decisionContext .Variable = entities.Variable {}
184
182
var featureDecision decision.FeatureDecision
185
- var reasons decide.DecisionReasons
186
- var experimentID string
187
- var variationID string
188
- var useCMAB bool
189
-
190
- // To avoid cyclo-complexity warning
191
- findRegularDecision := func () {
192
- // regular decision
193
- featureDecision , reasons , err = o .DecisionService .GetFeatureDecision (decisionContext , usrContext , & allOptions )
194
- decisionReasons .Append (reasons )
195
- }
196
-
197
- if o .cmabService != nil {
198
- for _ , experimentID := range feature .ExperimentIDs {
199
- experiment , err := projectConfig .GetExperimentByID (experimentID )
200
-
201
- if err == nil && experiment .Cmab != nil {
202
- cmabDecision , cmabErr := o .cmabService .GetDecision (projectConfig , usrContext , experiment .ID , & allOptions )
183
+ var decisionReasonsList decide.DecisionReasons // Fix shadowing - renamed from "reasons"
203
184
204
- // Handle CMAB error properly - check for errors BEFORE using the decision
205
- if cmabErr != nil {
206
- o .logger .Warning (fmt .Sprintf ("CMAB decision failed for experiment %s: %v" , experiment .ID , cmabErr ))
207
- continue // Skip to next experiment or fall back to regular decision
208
- }
185
+ // Try CMAB decision first
186
+ useCMAB := o .tryGetCMABDecision (feature , projectConfig , usrContext , & allOptions , decisionReasons , & featureDecision )
209
187
210
- if selectedVariation , exists := experiment . Variations [ cmabDecision . VariationID ]; exists {
211
- featureDecision = decision. FeatureDecision {
212
- Decision : decision. Decision { Reason : "CMAB decision" },
213
- Variation : & selectedVariation ,
214
- Experiment : experiment ,
215
- Source : decision . FeatureTest ,
216
- CmabUUID : & cmabDecision . CmabUUID ,
217
- }
218
- useCMAB = true
219
- decisionReasons . AddInfo ( "Used CMAB service for decision" )
220
- break
188
+ // Fall back to other decision types if CMAB didn't work
189
+ if ! useCMAB {
190
+ // To avoid cyclo-complexity warning - forced decision logic
191
+ findForcedDecision := func () bool {
192
+ if userContext . forcedDecisionService != nil {
193
+ var variation * entities. Variation
194
+ var forcedErr error
195
+ variation , decisionReasonsList , forcedErr = userContext . forcedDecisionService . FindValidatedForcedDecision ( projectConfig , decision. OptimizelyDecisionContext { FlagKey : key , RuleKey : "" }, & allOptions ) // Fix shadowing by using assignment instead of declaration
196
+ decisionReasons . Append ( decisionReasonsList )
197
+ if forcedErr != nil {
198
+ return false
221
199
} else {
222
- o .logger .Warning (fmt .Sprintf ("CMAB returned invalid variation ID %s for experiment %s" , cmabDecision .VariationID , experiment .ID ))
200
+ featureDecision = decision.FeatureDecision {Decision : decision.Decision {Reason : pkgReasons .ForcedDecisionFound }, Variation : variation , Source : decision .FeatureTest }
201
+ return true
223
202
}
224
- } else {
225
- o .logger .Warning (fmt .Sprintf ("CMAB decision failed for experiment %s: %v" , experiment .ID , err ))
226
203
}
204
+ return false
227
205
}
228
- }
229
206
230
- // Only do regular decision logic if CMAB didn't work
231
- if ! useCMAB {
232
- // check forced-decisions first
233
- // Passing empty rule-key because checking mapping with flagKey only
234
- if userContext .forcedDecisionService != nil {
235
- var variation * entities.Variation
236
- variation , reasons , err = userContext .forcedDecisionService .FindValidatedForcedDecision (projectConfig , decision.OptimizelyDecisionContext {FlagKey : key , RuleKey : "" }, & allOptions )
237
- decisionReasons .Append (reasons )
238
- if err != nil {
239
- findRegularDecision ()
240
- } else {
241
- featureDecision = decision.FeatureDecision {Decision : decision.Decision {Reason : pkgReasons .ForcedDecisionFound }, Variation : variation , Source : decision .FeatureTest }
242
- }
243
- } else {
207
+ // To avoid cyclo-complexity warning - regular decision logic
208
+ findRegularDecision := func () {
209
+ // regular decision
210
+ featureDecision , decisionReasonsList , err = o .DecisionService .GetFeatureDecision (decisionContext , usrContext , & allOptions )
211
+ decisionReasons .Append (decisionReasonsList )
212
+ }
213
+
214
+ if ! findForcedDecision () {
244
215
findRegularDecision ()
245
216
}
246
217
}
@@ -249,38 +220,99 @@ func (o *OptimizelyClient) decide(userContext *OptimizelyUserContext, key string
249
220
return o .handleDecisionServiceError (err , key , * userContext )
250
221
}
251
222
223
+ return o .buildDecisionResponse (featureDecision , feature , key , userContext , & allOptions , decisionReasons , decisionContext )
224
+ }
225
+
226
+ // tryGetCMABDecision attempts to get a CMAB decision for the feature
227
+ func (o * OptimizelyClient ) tryGetCMABDecision (feature entities.Feature , projectConfig config.ProjectConfig , usrContext entities.UserContext , options * decide.Options , decisionReasons decide.DecisionReasons , featureDecision * decision.FeatureDecision ) bool {
228
+ if o .cmabService == nil {
229
+ return false
230
+ }
231
+
232
+ for _ , experimentID := range feature .ExperimentIDs {
233
+ experiment , expErr := projectConfig .GetExperimentByID (experimentID ) // Fix shadowing
234
+
235
+ // Handle CMAB error properly - check for errors BEFORE using the experiment
236
+ if expErr == nil && experiment .Cmab != nil {
237
+ cmabDecision , cmabErr := o .cmabService .GetDecision (projectConfig , usrContext , experiment .ID , options )
238
+
239
+ // Handle CMAB service errors gracefully - log and continue to next experiment
240
+ if cmabErr != nil {
241
+ o .logger .Warning (fmt .Sprintf ("CMAB decision failed for experiment %s: %v" , experiment .ID , cmabErr ))
242
+ continue
243
+ }
244
+
245
+ // Validate CMAB response - ensure variation exists before using it
246
+ if selectedVariation , exists := experiment .Variations [cmabDecision .VariationID ]; exists {
247
+ * featureDecision = decision.FeatureDecision {
248
+ Decision : decision.Decision {Reason : "CMAB decision" },
249
+ Variation : & selectedVariation ,
250
+ Experiment : experiment ,
251
+ Source : decision .FeatureTest ,
252
+ CmabUUID : & cmabDecision .CmabUUID , // Include CMAB UUID for tracking
253
+ }
254
+ decisionReasons .AddInfo ("Used CMAB service for decision" )
255
+ return true
256
+ } else {
257
+ // Log invalid variation ID returned by CMAB service
258
+ o .logger .Warning (fmt .Sprintf ("CMAB returned invalid variation ID %s for experiment %s" , cmabDecision .VariationID , experiment .ID ))
259
+ }
260
+ }
261
+ }
262
+ return false
263
+ }
264
+
265
+ // buildDecisionResponse constructs the final OptimizelyDecision response
266
+ func (o * OptimizelyClient ) buildDecisionResponse (featureDecision decision.FeatureDecision , feature entities.Feature , key string , userContext * OptimizelyUserContext , options * decide.Options , decisionReasons decide.DecisionReasons , decisionContext decision.FeatureDecisionContext ) OptimizelyDecision {
267
+ var variationKey string
268
+ var eventSent , flagEnabled bool
269
+ var experimentID , variationID string
270
+
252
271
if featureDecision .Variation != nil {
253
272
variationKey = featureDecision .Variation .Key
254
273
flagEnabled = featureDecision .Variation .FeatureEnabled
255
274
experimentID = featureDecision .Experiment .ID
256
275
variationID = featureDecision .Variation .ID
257
276
}
258
277
259
- if ! allOptions .DisableDecisionEvent {
278
+ usrContext := entities.UserContext {
279
+ ID : userContext .GetUserID (),
280
+ Attributes : userContext .GetUserAttributes (),
281
+ QualifiedSegments : userContext .GetQualifiedSegments (),
282
+ }
283
+
284
+ // Send impression event
285
+ if ! options .DisableDecisionEvent {
260
286
if ue , ok := event .CreateImpressionUserEvent (decisionContext .ProjectConfig , featureDecision .Experiment ,
261
287
featureDecision .Variation , usrContext , key , featureDecision .Experiment .Key , featureDecision .Source , flagEnabled , featureDecision .CmabUUID ); ok {
262
288
o .EventProcessor .ProcessEvent (ue )
263
289
eventSent = true
264
290
}
265
291
}
266
292
293
+ // Get variable map
267
294
variableMap := map [string ]interface {}{}
268
- if ! allOptions .ExcludeVariables {
295
+ if ! options .ExcludeVariables {
296
+ var reasons decide.DecisionReasons
269
297
variableMap , reasons = o .getDecisionVariableMap (feature , featureDecision .Variation , flagEnabled )
270
298
decisionReasons .Append (reasons )
271
299
}
272
- optimizelyJSON := optimizelyjson .NewOptimizelyJSONfromMap (variableMap )
273
- reasonsToReport := decisionReasons .ToReport ()
274
- ruleKey := featureDecision .Experiment .Key
275
300
301
+ // Send notification
276
302
if o .notificationCenter != nil {
303
+ reasonsToReport := decisionReasons .ToReport ()
304
+ ruleKey := featureDecision .Experiment .Key
277
305
decisionNotification := decision .FlagNotification (key , variationKey , ruleKey , experimentID , variationID , flagEnabled , eventSent , usrContext , variableMap , reasonsToReport )
278
306
o .logger .Debug (fmt .Sprintf (`Feature %q is enabled for user %q? %v` , key , usrContext .ID , flagEnabled ))
279
307
if e := o .notificationCenter .Send (notification .Decision , * decisionNotification ); e != nil {
280
308
o .logger .Warning ("Problem with sending notification" )
281
309
}
282
310
}
283
311
312
+ optimizelyJSON := optimizelyjson .NewOptimizelyJSONfromMap (variableMap )
313
+ reasonsToReport := decisionReasons .ToReport ()
314
+ ruleKey := featureDecision .Experiment .Key
315
+
284
316
return NewOptimizelyDecision (variationKey , ruleKey , key , flagEnabled , optimizelyJSON , * userContext , reasonsToReport )
285
317
}
286
318
@@ -509,7 +541,7 @@ func (o *OptimizelyClient) Activate(experimentKey string, userContext entities.U
509
541
}
510
542
511
543
// IsFeatureEnabled returns true if the feature is enabled for the given user. If the user is part of a feature test
512
- // then an impression event will be queued up to be sent to the Optimizely log endpoint for results processing.
544
+ // then an impression event will be queued up to the Optimizely log endpoint for results processing.
513
545
func (o * OptimizelyClient ) IsFeatureEnabled (featureKey string , userContext entities.UserContext ) (result bool , err error ) {
514
546
515
547
defer func () {
0 commit comments