Skip to content

Commit cf2b2a4

Browse files
author
Michael Ng
authored
feat(activate): Add Activate and GetVariation APIs. (#122)
1 parent 1499322 commit cf2b2a4

11 files changed

+518
-144
lines changed

optimizely/client/client.go

Lines changed: 153 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,41 @@ type OptimizelyClient struct {
4242
executionCtx utils.ExecutionCtx
4343
}
4444

45+
// Activate returns the key of the variation the user is bucketed into and sends an impression event to the Optimizely log endpoint
46+
func (o *OptimizelyClient) Activate(experimentKey string, userContext entities.UserContext) (result string, err error) {
47+
48+
defer func() {
49+
if r := recover(); r != nil {
50+
switch t := r.(type) {
51+
case error:
52+
err = t
53+
case string:
54+
err = errors.New(t)
55+
default:
56+
err = errors.New("unexpected error")
57+
}
58+
errorMessage := fmt.Sprintf("optimizely SDK is panicking with the error:")
59+
logger.Error(errorMessage, err)
60+
logger.Debug(string(debug.Stack()))
61+
}
62+
}()
63+
64+
decisionContext, experimentDecision, err := o.getExperimentDecision(experimentKey, userContext)
65+
if err != nil {
66+
logger.Error("received an error while computing experiment decision", err)
67+
return result, err
68+
}
69+
70+
if experimentDecision.Variation != nil {
71+
// send an impression event
72+
result = experimentDecision.Variation.Key
73+
impressionEvent := event.CreateImpressionUserEvent(decisionContext.ProjectConfig, *decisionContext.Experiment, *experimentDecision.Variation, userContext)
74+
o.EventProcessor.ProcessEvent(impressionEvent)
75+
}
76+
77+
return result, err
78+
}
79+
4580
// IsFeatureEnabled returns true if the feature is enabled for the given user
4681
func (o *OptimizelyClient) IsFeatureEnabled(featureKey string, userContext entities.UserContext) (result bool, err error) {
4782

@@ -124,45 +159,6 @@ func (o *OptimizelyClient) GetEnabledFeatures(userContext entities.UserContext)
124159
return enabledFeatures, err
125160
}
126161

127-
// Track take and event key with event tags and if the event is part of the config, send to events backend.
128-
func (o *OptimizelyClient) Track(eventKey string, userContext entities.UserContext, eventTags map[string]interface{}) (err error) {
129-
130-
defer func() {
131-
if r := recover(); r != nil {
132-
switch t := r.(type) {
133-
case error:
134-
err = t
135-
case string:
136-
err = errors.New(t)
137-
default:
138-
err = errors.New("unexpected error")
139-
}
140-
errorMessage := fmt.Sprintf("optimizely SDK is panicking with the error:")
141-
logger.Error(errorMessage, err)
142-
logger.Debug(string(debug.Stack()))
143-
}
144-
}()
145-
146-
projectConfig, err := o.GetProjectConfig()
147-
if err != nil {
148-
logger.Error("Optimizely SDK tracking error", err)
149-
return err
150-
}
151-
152-
configEvent, err := projectConfig.GetEventByKey(eventKey)
153-
154-
if err == nil {
155-
userEvent := event.CreateConversionUserEvent(projectConfig, configEvent, userContext, eventTags)
156-
o.EventProcessor.ProcessEvent(userEvent)
157-
} else {
158-
errorMessage := fmt.Sprintf(`optimizely SDK track: error getting event with key "%s"`, eventKey)
159-
logger.Error(errorMessage, err)
160-
return err
161-
}
162-
163-
return err
164-
}
165-
166162
// GetFeatureVariableBoolean returns boolean feature variable value
167163
func (o *OptimizelyClient) GetFeatureVariableBoolean(featureKey, variableKey string, userContext entities.UserContext) (value bool, err error) {
168164

@@ -364,43 +360,106 @@ func (o *OptimizelyClient) GetAllFeatureVariables(featureKey string, userContext
364360
return enabled, variableMap, err
365361
}
366362

367-
func (o *OptimizelyClient) getFeatureDecision(featureKey string, userContext entities.UserContext) (decisionContext decision.FeatureDecisionContext, featureDecision decision.FeatureDecision, err error) {
363+
// GetVariation returns the key of the variation the user is bucketed into
364+
func (o *OptimizelyClient) GetVariation(experimentKey string, userContext entities.UserContext) (result string, err error) {
368365

369366
defer func() {
370-
var e error
371367
if r := recover(); r != nil {
372368
switch t := r.(type) {
373369
case error:
374-
e = t
370+
err = t
375371
case string:
376-
e = errors.New(t)
372+
err = errors.New(t)
377373
default:
378-
e = errors.New("unexpected error")
374+
err = errors.New("unexpected error")
379375
}
380376
errorMessage := fmt.Sprintf("optimizely SDK is panicking with the error:")
381-
logger.Error(errorMessage, e)
377+
logger.Error(errorMessage, err)
382378
logger.Debug(string(debug.Stack()))
379+
}
380+
}()
383381

384-
// If we have a feature, then we can recover w/o throwing
385-
if decisionContext.Feature == nil {
386-
err = e
382+
_, experimentDecision, err := o.getExperimentDecision(experimentKey, userContext)
383+
if err != nil {
384+
logger.Error("received an error while computing experiment decision", err)
385+
}
386+
387+
if experimentDecision.Variation != nil {
388+
result = experimentDecision.Variation.Key
389+
}
390+
391+
return result, err
392+
}
393+
394+
// Track take and event key with event tags and if the event is part of the config, send to events backend.
395+
func (o *OptimizelyClient) Track(eventKey string, userContext entities.UserContext, eventTags map[string]interface{}) (err error) {
396+
397+
defer func() {
398+
if r := recover(); r != nil {
399+
switch t := r.(type) {
400+
case error:
401+
err = t
402+
case string:
403+
err = errors.New(t)
404+
default:
405+
err = errors.New("unexpected error")
406+
}
407+
errorMessage := fmt.Sprintf("optimizely SDK is panicking with the error:")
408+
logger.Error(errorMessage, err)
409+
logger.Debug(string(debug.Stack()))
410+
}
411+
}()
412+
413+
projectConfig, e := o.GetProjectConfig()
414+
if e != nil {
415+
logger.Error("Optimizely SDK tracking error", e)
416+
return e
417+
}
418+
419+
configEvent, e := projectConfig.GetEventByKey(eventKey)
420+
421+
if e != nil {
422+
errorMessage := fmt.Sprintf(`optimizely SDK track: error getting event with key "%s"`, eventKey)
423+
logger.Error(errorMessage, e)
424+
return e
425+
}
426+
427+
userEvent := event.CreateConversionUserEvent(projectConfig, configEvent, userContext, eventTags)
428+
o.EventProcessor.ProcessEvent(userEvent)
429+
return nil
430+
}
431+
432+
func (o *OptimizelyClient) getFeatureDecision(featureKey string, userContext entities.UserContext) (decisionContext decision.FeatureDecisionContext, featureDecision decision.FeatureDecision, err error) {
433+
434+
defer func() {
435+
if r := recover(); r != nil {
436+
switch t := r.(type) {
437+
case error:
438+
err = t
439+
case string:
440+
err = errors.New(t)
441+
default:
442+
err = errors.New("unexpected error")
387443
}
444+
errorMessage := fmt.Sprintf("optimizely SDK is panicking with the error:")
445+
logger.Error(errorMessage, err)
446+
logger.Debug(string(debug.Stack()))
388447
}
389448
}()
390449

391450
userID := userContext.ID
392451
logger.Debug(fmt.Sprintf(`Evaluating feature "%s" for user "%s".`, featureKey, userID))
393452

394-
projectConfig, err := o.GetProjectConfig()
395-
if err != nil {
396-
logger.Error("Error calling getFeatureDecision", err)
397-
return decisionContext, featureDecision, err
453+
projectConfig, e := o.GetProjectConfig()
454+
if e != nil {
455+
logger.Error("Error calling getFeatureDecision", e)
456+
return decisionContext, featureDecision, e
398457
}
399458

400-
feature, err := projectConfig.GetFeatureByKey(featureKey)
401-
if err != nil {
402-
logger.Error("Error calling getFeatureDecision", err)
403-
return decisionContext, featureDecision, err
459+
feature, e := projectConfig.GetFeatureByKey(featureKey)
460+
if e != nil {
461+
logger.Error("Error calling getFeatureDecision", e)
462+
return decisionContext, featureDecision, e
404463
}
405464

406465
decisionContext = decision.FeatureDecisionContext{
@@ -410,15 +469,51 @@ func (o *OptimizelyClient) getFeatureDecision(featureKey string, userContext ent
410469

411470
featureDecision, err = o.DecisionService.GetFeatureDecision(decisionContext, userContext)
412471
if err != nil {
413-
err = nil
414472
logger.Warning("error making a decision")
415473
return decisionContext, featureDecision, err
416474
}
417475

418-
// @TODO(yasir): send decision notification
419476
return decisionContext, featureDecision, err
420477
}
421478

479+
func (o *OptimizelyClient) getExperimentDecision(experimentKey string, userContext entities.UserContext) (decisionContext decision.ExperimentDecisionContext, experimentDecision decision.ExperimentDecision, err error) {
480+
481+
userID := userContext.ID
482+
logger.Debug(fmt.Sprintf(`Evaluating experiment "%s" for user "%s".`, experimentKey, userID))
483+
484+
projectConfig, e := o.GetProjectConfig()
485+
if e != nil {
486+
logger.Error("Error calling getExperimentDecision", e)
487+
return decisionContext, experimentDecision, e
488+
}
489+
490+
experiment, e := projectConfig.GetExperimentByKey(experimentKey)
491+
if e != nil {
492+
logger.Error("Error calling getExperimentDecision", e)
493+
return decisionContext, experimentDecision, e
494+
}
495+
496+
decisionContext = decision.ExperimentDecisionContext{
497+
Experiment: &experiment,
498+
ProjectConfig: projectConfig,
499+
}
500+
501+
experimentDecision, err = o.DecisionService.GetExperimentDecision(decisionContext, userContext)
502+
if err != nil {
503+
logger.Warning(fmt.Sprintf(`error making a decision for experiment "%s"`, experimentKey))
504+
return decisionContext, experimentDecision, err
505+
}
506+
507+
if experimentDecision.Variation != nil {
508+
result := experimentDecision.Variation.Key
509+
logger.Info(fmt.Sprintf(`User "%s" is bucketed into variation "%s" of experiment "%s".`, userContext.ID, result, experimentKey))
510+
} else {
511+
logger.Info(fmt.Sprintf(`User "%s" is not bucketed into any variation for experiment "%s".`, userContext.ID, experimentKey))
512+
}
513+
514+
return decisionContext, experimentDecision, err
515+
}
516+
422517
// GetProjectConfig returns the current ProjectConfig or nil if the instance is not valid
423518
func (o *OptimizelyClient) GetProjectConfig() (projectConfig optimizely.ProjectConfig, err error) {
424519

0 commit comments

Comments
 (0)