@@ -46,7 +46,7 @@ type OptimizelyClient struct {
4646 notificationCenter notification.Center
4747 execGroup * utils.ExecGroup
4848 logger logging.OptimizelyLogProducer
49- defaultDecideOptions decide.OptimizelyDecideOptions
49+ defaultDecideOptions * decide.Options
5050}
5151
5252// CreateUserContext creates a context of the user for which decision APIs will be called.
@@ -55,6 +55,156 @@ func (o *OptimizelyClient) CreateUserContext(userID string, attributes map[strin
5555 return newOptimizelyUserContext (o , userID , attributes )
5656}
5757
58+ func (o * OptimizelyClient ) decide (userContext OptimizelyUserContext , key string , options * decide.Options ) OptimizelyDecision {
59+ var err error
60+ defer func () {
61+ if r := recover (); r != nil {
62+ switch t := r .(type ) {
63+ case error :
64+ err = t
65+ case string :
66+ err = errors .New (t )
67+ default :
68+ err = errors .New ("unexpected error" )
69+ }
70+ errorMessage := fmt .Sprintf ("decide call, optimizely SDK is panicking with the error:" )
71+ o .logger .Error (errorMessage , err )
72+ o .logger .Debug (string (debug .Stack ()))
73+ }
74+ }()
75+
76+ decisionContext := decision.FeatureDecisionContext {}
77+ projectConfig , err := o .getProjectConfig ()
78+ if err != nil {
79+ return NewErrorDecision (key , userContext , decide .GetDecideError (decide .SDKNotReady ))
80+ }
81+ decisionContext .ProjectConfig = projectConfig
82+
83+ feature , err := projectConfig .GetFeatureByKey (key )
84+ if err != nil {
85+ return NewErrorDecision (key , userContext , decide .GetDecideError (decide .FlagKeyInvalid , key ))
86+ }
87+ decisionContext .Feature = & feature
88+
89+ usrContext := entities.UserContext {
90+ ID : userContext .GetUserID (),
91+ Attributes : userContext .GetUserAttributes (),
92+ }
93+ var variationKey string
94+ var eventSent , flagEnabled bool
95+ allOptions := o .getAllOptions (options )
96+ decisionReasons := decide .NewDecisionReasons (& allOptions )
97+ decisionContext .Variable = entities.Variable {}
98+
99+ featureDecision , err := o .DecisionService .GetFeatureDecision (decisionContext , usrContext , & allOptions , decisionReasons )
100+ if err != nil {
101+ o .logger .Warning (fmt .Sprintf (`Received error while making a decision for feature "%s": %s` , key , err ))
102+ }
103+
104+ if featureDecision .Variation != nil {
105+ variationKey = featureDecision .Variation .Key
106+ flagEnabled = featureDecision .Variation .FeatureEnabled
107+ }
108+
109+ if ! allOptions .DisableDecisionEvent {
110+ if ue , ok := event .CreateImpressionUserEvent (decisionContext .ProjectConfig , featureDecision .Experiment ,
111+ featureDecision .Variation , usrContext , key , featureDecision .Experiment .Key , featureDecision .Source , flagEnabled ); ok {
112+ o .EventProcessor .ProcessEvent (ue )
113+ eventSent = true
114+ }
115+ }
116+
117+ variableMap := map [string ]interface {}{}
118+ if ! allOptions .ExcludeVariables {
119+ variableMap = o .getDecisionVariableMap (feature , featureDecision .Variation , flagEnabled , decisionReasons )
120+ }
121+ optimizelyJSON := optimizelyjson .NewOptimizelyJSONfromMap (variableMap )
122+ reasonsToReport := decisionReasons .ToReport ()
123+ ruleKey := featureDecision .Experiment .Key
124+
125+ if o .notificationCenter != nil {
126+ decisionNotification := decision .FlagNotification (key , variationKey , ruleKey , flagEnabled , eventSent , usrContext , variableMap , reasonsToReport )
127+ o .logger .Info (fmt .Sprintf (`Feature "%s" is enabled for user "%s"? %v` , key , usrContext .ID , flagEnabled ))
128+ if e := o .notificationCenter .Send (notification .Decision , * decisionNotification ); e != nil {
129+ o .logger .Warning ("Problem with sending notification" )
130+ }
131+ }
132+
133+ return NewOptimizelyDecision (variationKey , ruleKey , key , flagEnabled , optimizelyJSON , userContext , reasonsToReport )
134+ }
135+
136+ func (o * OptimizelyClient ) decideForKeys (userContext OptimizelyUserContext , keys []string , options * decide.Options ) map [string ]OptimizelyDecision {
137+ var err error
138+ defer func () {
139+ if r := recover (); r != nil {
140+ switch t := r .(type ) {
141+ case error :
142+ err = t
143+ case string :
144+ err = errors .New (t )
145+ default :
146+ err = errors .New ("unexpected error" )
147+ }
148+ errorMessage := fmt .Sprintf ("decideForKeys call, optimizely SDK is panicking with the error:" )
149+ o .logger .Error (errorMessage , err )
150+ o .logger .Debug (string (debug .Stack ()))
151+ }
152+ }()
153+
154+ decisionMap := map [string ]OptimizelyDecision {}
155+ if _ , err = o .getProjectConfig (); err != nil {
156+ o .logger .Error ("Optimizely instance is not valid, failing decideForKeys call." , err )
157+ return decisionMap
158+ }
159+
160+ if len (keys ) == 0 {
161+ return decisionMap
162+ }
163+
164+ enabledFlagsOnly := o .getAllOptions (options ).EnabledFlagsOnly
165+ for _ , key := range keys {
166+ optimizelyDecision := o .decide (userContext , key , options )
167+ if ! enabledFlagsOnly || optimizelyDecision .GetEnabled () {
168+ decisionMap [key ] = optimizelyDecision
169+ }
170+ }
171+
172+ return decisionMap
173+ }
174+
175+ func (o * OptimizelyClient ) decideAll (userContext OptimizelyUserContext , options * decide.Options ) map [string ]OptimizelyDecision {
176+
177+ var err error
178+ defer func () {
179+ if r := recover (); r != nil {
180+ switch t := r .(type ) {
181+ case error :
182+ err = t
183+ case string :
184+ err = errors .New (t )
185+ default :
186+ err = errors .New ("unexpected error" )
187+ }
188+ errorMessage := fmt .Sprintf ("decideAll call, optimizely SDK is panicking with the error:" )
189+ o .logger .Error (errorMessage , err )
190+ o .logger .Debug (string (debug .Stack ()))
191+ }
192+ }()
193+
194+ projectConfig , err := o .getProjectConfig ()
195+ if err != nil {
196+ o .logger .Error ("Optimizely instance is not valid, failing decideAll call." , err )
197+ return map [string ]OptimizelyDecision {}
198+ }
199+
200+ allFlagKeys := []string {}
201+ for _ , flag := range projectConfig .GetFeatureList () {
202+ allFlagKeys = append (allFlagKeys , flag .Key )
203+ }
204+
205+ return o .decideForKeys (userContext , allFlagKeys , options )
206+ }
207+
58208// Activate returns the key of the variation the user is bucketed into and queues up an impression event to be sent to
59209// the Optimizely log endpoint for results processing.
60210func (o * OptimizelyClient ) Activate (experimentKey string , userContext entities.UserContext ) (result string , err error ) {
@@ -657,7 +807,7 @@ func (o *OptimizelyClient) getFeatureDecision(featureKey, variableKey string, us
657807 }
658808
659809 decisionContext .Variable = variable
660- options := decide.OptimizelyDecideOptions {}
810+ options := & decide.Options {}
661811 featureDecision , err = o .DecisionService .GetFeatureDecision (decisionContext , userContext , options , decide .NewDecisionReasons (options ))
662812 if err != nil {
663813 o .logger .Warning (fmt .Sprintf (`Received error while making a decision for feature "%s": %s` , featureKey , err ))
@@ -688,7 +838,7 @@ func (o *OptimizelyClient) getExperimentDecision(experimentKey string, userConte
688838 ProjectConfig : projectConfig ,
689839 }
690840
691- options := decide.OptimizelyDecideOptions {}
841+ options := & decide.Options {}
692842 experimentDecision , err = o .DecisionService .GetExperimentDecision (decisionContext , userContext , options , decide .NewDecisionReasons (options ))
693843 if err != nil {
694844 o .logger .Warning (fmt .Sprintf (`Received error while making a decision for experiment "%s": %s` , experimentKey , err ))
@@ -776,18 +926,49 @@ func (o *OptimizelyClient) getProjectConfig() (projectConfig config.ProjectConfi
776926 return projectConfig , nil
777927}
778928
929+ func (o * OptimizelyClient ) getAllOptions (options * decide.Options ) decide.Options {
930+ return decide.Options {
931+ DisableDecisionEvent : o .defaultDecideOptions .DisableDecisionEvent || options .DisableDecisionEvent ,
932+ EnabledFlagsOnly : o .defaultDecideOptions .EnabledFlagsOnly || options .EnabledFlagsOnly ,
933+ ExcludeVariables : o .defaultDecideOptions .ExcludeVariables || options .ExcludeVariables ,
934+ IgnoreUserProfileService : o .defaultDecideOptions .IgnoreUserProfileService || options .IgnoreUserProfileService ,
935+ IncludeReasons : o .defaultDecideOptions .IncludeReasons || options .IncludeReasons ,
936+ }
937+ }
938+
779939// GetOptimizelyConfig returns OptimizelyConfig object
780940func (o * OptimizelyClient ) GetOptimizelyConfig () (optimizelyConfig * config.OptimizelyConfig ) {
781941
782942 return o .ConfigManager .GetOptimizelyConfig ()
783-
784943}
785944
786945// Close closes the Optimizely instance and stops any ongoing tasks from its children components.
787946func (o * OptimizelyClient ) Close () {
788947 o .execGroup .TerminateAndWait ()
789948}
790949
950+ func (o * OptimizelyClient ) getDecisionVariableMap (feature entities.Feature , variation * entities.Variation , featureEnabled bool , decisionReasons decide.DecisionReasons ) map [string ]interface {} {
951+ valuesMap := map [string ]interface {}{}
952+
953+ for _ , v := range feature .VariableMap {
954+ val := v .DefaultValue
955+
956+ if featureEnabled {
957+ if variable , ok := variation .Variables [v .ID ]; ok {
958+ val = variable .Value
959+ }
960+ }
961+
962+ typedValue , typedError := o .getTypedValue (val , v .Type )
963+ if typedError != nil {
964+ decisionReasons .AddError (decide .GetDecideMessage (decide .VariableValueInvalid , v .Key ))
965+ }
966+ valuesMap [v .Key ] = typedValue
967+ }
968+
969+ return valuesMap
970+ }
971+
791972func isNil (v interface {}) bool {
792973 return v == nil || (reflect .ValueOf (v ).Kind () == reflect .Ptr && reflect .ValueOf (v ).IsNil ())
793974}
0 commit comments