Skip to content

Commit

Permalink
Merge 884edde into e113a70
Browse files Browse the repository at this point in the history
  • Loading branch information
yasirfolio3 committed Dec 2, 2020
2 parents e113a70 + 884edde commit 25540b6
Show file tree
Hide file tree
Showing 44 changed files with 1,441 additions and 184 deletions.
189 changes: 185 additions & 4 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type OptimizelyClient struct {
notificationCenter notification.Center
execGroup *utils.ExecGroup
logger logging.OptimizelyLogProducer
defaultDecideOptions decide.OptimizelyDecideOptions
defaultDecideOptions *decide.Options
}

// Decide API
Expand All @@ -57,6 +57,156 @@ func (o *OptimizelyClient) CreateUserContext(userID string, attributes map[strin
return newOptimizelyUserContext(o, userID, attributes)
}

func (o *OptimizelyClient) decide(userContext OptimizelyUserContext, key string, options *decide.Options) OptimizelyDecision {
var err error
defer func() {
if r := recover(); r != nil {
switch t := r.(type) {
case error:
err = t
case string:
err = errors.New(t)
default:
err = errors.New("unexpected error")
}
errorMessage := fmt.Sprintf("decide call, optimizely SDK is panicking with the error:")
o.logger.Error(errorMessage, err)
o.logger.Debug(string(debug.Stack()))
}
}()

decisionContext := decision.FeatureDecisionContext{}
projectConfig, err := o.getProjectConfig()
if err != nil {
return NewErrorDecision(key, userContext, decide.GetDecideError(decide.SDKNotReady))
}
decisionContext.ProjectConfig = projectConfig

feature, err := projectConfig.GetFeatureByKey(key)
if err != nil {
return NewErrorDecision(key, userContext, decide.GetDecideError(decide.FlagKeyInvalid, key))
}
decisionContext.Feature = &feature

usrContext := entities.UserContext{
ID: userContext.GetUserID(),
Attributes: userContext.GetUserAttributes(),
}
var variationKey string
var eventSent, flagEnabled bool
allOptions := o.getAllOptions(options)
decisionReasons := decide.NewDecisionReasons(&allOptions)
decisionContext.Variable = entities.Variable{}

featureDecision, err := o.DecisionService.GetFeatureDecision(decisionContext, usrContext, &allOptions, decisionReasons)
if err != nil {
o.logger.Warning(fmt.Sprintf(`Received error while making a decision for feature "%s": %s`, key, err))
}

if featureDecision.Variation != nil {
variationKey = featureDecision.Variation.Key
flagEnabled = featureDecision.Variation.FeatureEnabled
}

if !allOptions.DisableDecisionEvent {
if ue, ok := event.CreateImpressionUserEvent(decisionContext.ProjectConfig, featureDecision.Experiment,
featureDecision.Variation, usrContext, key, featureDecision.Experiment.Key, featureDecision.Source, flagEnabled); ok {
o.EventProcessor.ProcessEvent(ue)
eventSent = true
}
}

variableMap := map[string]interface{}{}
if !allOptions.ExcludeVariables {
variableMap = o.getDecisionVariableMap(feature, featureDecision.Variation, flagEnabled, decisionReasons)
}
optimizelyJSON := optimizelyjson.NewOptimizelyJSONfromMap(variableMap)
reasonsToReport := decisionReasons.ToReport()
ruleKey := featureDecision.Experiment.Key

if o.notificationCenter != nil {
decisionNotification := decision.FlagNotification(key, variationKey, ruleKey, flagEnabled, eventSent, usrContext, variableMap, reasonsToReport)
o.logger.Info(fmt.Sprintf(`Feature "%s" is enabled for user "%s"? %v`, key, usrContext.ID, flagEnabled))
if e := o.notificationCenter.Send(notification.Decision, *decisionNotification); e != nil {
o.logger.Warning("Problem with sending notification")
}
}

return NewOptimizelyDecision(variationKey, ruleKey, key, flagEnabled, optimizelyJSON, userContext, reasonsToReport)
}

func (o *OptimizelyClient) decideForKeys(userContext OptimizelyUserContext, keys []string, options *decide.Options) map[string]OptimizelyDecision {
var err error
defer func() {
if r := recover(); r != nil {
switch t := r.(type) {
case error:
err = t
case string:
err = errors.New(t)
default:
err = errors.New("unexpected error")
}
errorMessage := fmt.Sprintf("decideForKeys call, optimizely SDK is panicking with the error:")
o.logger.Error(errorMessage, err)
o.logger.Debug(string(debug.Stack()))
}
}()

decisionMap := map[string]OptimizelyDecision{}
if _, err = o.getProjectConfig(); err != nil {
o.logger.Error("Optimizely instance is not valid, failing decideForKeys call.", err)
return decisionMap
}

if len(keys) == 0 {
return decisionMap
}

enabledFlagsOnly := o.getAllOptions(options).EnabledFlagsOnly
for _, key := range keys {
optimizelyDecision := o.decide(userContext, key, options)
if !enabledFlagsOnly || optimizelyDecision.GetEnabled() {
decisionMap[key] = optimizelyDecision
}
}

return decisionMap
}

func (o *OptimizelyClient) decideAll(userContext OptimizelyUserContext, options *decide.Options) map[string]OptimizelyDecision {

var err error
defer func() {
if r := recover(); r != nil {
switch t := r.(type) {
case error:
err = t
case string:
err = errors.New(t)
default:
err = errors.New("unexpected error")
}
errorMessage := fmt.Sprintf("decideAll call, optimizely SDK is panicking with the error:")
o.logger.Error(errorMessage, err)
o.logger.Debug(string(debug.Stack()))
}
}()

projectConfig, err := o.getProjectConfig()
if err != nil {
o.logger.Error("Optimizely instance is not valid, failing decideAll call.", err)
return map[string]OptimizelyDecision{}
}

allFlagKeys := []string{}
for _, flag := range projectConfig.GetFeatureList() {
allFlagKeys = append(allFlagKeys, flag.Key)
}

return o.decideForKeys(userContext, allFlagKeys, options)
}

// Activate returns the key of the variation the user is bucketed into and queues up an impression event to be sent to
// the Optimizely log endpoint for results processing.
func (o *OptimizelyClient) Activate(experimentKey string, userContext entities.UserContext) (result string, err error) {
Expand Down Expand Up @@ -659,7 +809,7 @@ func (o *OptimizelyClient) getFeatureDecision(featureKey, variableKey string, us
}

decisionContext.Variable = variable
options := decide.OptimizelyDecideOptions{}
options := &decide.Options{}
featureDecision, err = o.DecisionService.GetFeatureDecision(decisionContext, userContext, options, decide.NewDecisionReasons(options))
if err != nil {
o.logger.Warning(fmt.Sprintf(`Received error while making a decision for feature "%s": %s`, featureKey, err))
Expand Down Expand Up @@ -690,7 +840,7 @@ func (o *OptimizelyClient) getExperimentDecision(experimentKey string, userConte
ProjectConfig: projectConfig,
}

options := decide.OptimizelyDecideOptions{}
options := &decide.Options{}
experimentDecision, err = o.DecisionService.GetExperimentDecision(decisionContext, userContext, options, decide.NewDecisionReasons(options))
if err != nil {
o.logger.Warning(fmt.Sprintf(`Received error while making a decision for experiment "%s": %s`, experimentKey, err))
Expand Down Expand Up @@ -778,18 +928,49 @@ func (o *OptimizelyClient) getProjectConfig() (projectConfig config.ProjectConfi
return projectConfig, nil
}

func (o *OptimizelyClient) getAllOptions(options *decide.Options) decide.Options {
return decide.Options{
DisableDecisionEvent: o.defaultDecideOptions.DisableDecisionEvent || options.DisableDecisionEvent,
EnabledFlagsOnly: o.defaultDecideOptions.EnabledFlagsOnly || options.EnabledFlagsOnly,
ExcludeVariables: o.defaultDecideOptions.ExcludeVariables || options.ExcludeVariables,
IgnoreUserProfileService: o.defaultDecideOptions.IgnoreUserProfileService || options.IgnoreUserProfileService,
IncludeReasons: o.defaultDecideOptions.IncludeReasons || options.IncludeReasons,
}
}

// GetOptimizelyConfig returns OptimizelyConfig object
func (o *OptimizelyClient) GetOptimizelyConfig() (optimizelyConfig *config.OptimizelyConfig) {

return o.ConfigManager.GetOptimizelyConfig()

}

// Close closes the Optimizely instance and stops any ongoing tasks from its children components.
func (o *OptimizelyClient) Close() {
o.execGroup.TerminateAndWait()
}

func (o *OptimizelyClient) getDecisionVariableMap(feature entities.Feature, variation *entities.Variation, featureEnabled bool, decisionReasons decide.DecisionReasons) map[string]interface{} {
valuesMap := map[string]interface{}{}

for _, v := range feature.VariableMap {
val := v.DefaultValue

if featureEnabled {
if variable, ok := variation.Variables[v.ID]; ok {
val = variable.Value
}
}

typedValue, typedError := o.getTypedValue(val, v.Type)
if typedError != nil {
decisionReasons.AddError(decide.GetDecideMessage(decide.VariableValueInvalid, v.Key))
}
valuesMap[v.Key] = typedValue
}

return valuesMap
}

func isNil(v interface{}) bool {
return v == nil || (reflect.ValueOf(v).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil())
}
Loading

0 comments on commit 25540b6

Please sign in to comment.