Skip to content

Commit

Permalink
add support for batch UPS for decideAll & decideForKeys
Browse files Browse the repository at this point in the history
  • Loading branch information
pulak-opti committed Oct 22, 2024
1 parent 6a21959 commit cfed748
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 9 deletions.
22 changes: 21 additions & 1 deletion pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type OptimizelyClient struct {
ctx context.Context
ConfigManager config.ProjectConfigManager
DecisionService decision.Service
UserProfileService decision.UserProfileService
EventProcessor event.Processor
OdpManager odp.Manager
notificationCenter notification.Center
Expand Down Expand Up @@ -153,6 +154,7 @@ func (o *OptimizelyClient) decide(userContext OptimizelyUserContext, key string,

decisionContext := decision.FeatureDecisionContext{
ForcedDecisionService: userContext.forcedDecisionService,
UserProfile: userContext.userProfile,
}
projectConfig, err := o.getProjectConfig()
if err != nil {
Expand Down Expand Up @@ -268,15 +270,33 @@ func (o *OptimizelyClient) decideForKeys(userContext OptimizelyUserContext, keys
if len(keys) == 0 {
return decisionMap
}
allOptions := o.getAllOptions(options)

var userProfile *decision.UserProfile
userProfileLen := 0
ignoreUserProfileSvc := o.UserProfileService == nil || allOptions.IgnoreUserProfileService
if !ignoreUserProfileSvc {
up := o.UserProfileService.Lookup(userContext.GetUserID())
userProfile = &up
userContext.userProfile = userProfile
userProfileLen = len(userProfile.ExperimentBucketMap)
}

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

if !ignoreUserProfileSvc {
isUserProfileUpdated := userProfile != nil && len(userProfile.ExperimentBucketMap) != userProfileLen
if isUserProfileUpdated {
o.UserProfileService.Save(*userProfile)
}
}

return decisionMap
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/client/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ func (f *OptimizelyFactory) Client(clientOptions ...OptionFunc) (*OptimizelyClie
appClient.EventProcessor = event.NewBatchEventProcessor(eventProcessorOptions...)
}

if f.userProfileService != nil {
appClient.UserProfileService = f.userProfileService
}

if f.decisionService != nil {
appClient.DecisionService = f.decisionService
} else {
Expand Down
7 changes: 6 additions & 1 deletion pkg/client/optimizely_user_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type OptimizelyUserContext struct {
qualifiedSegments []string
optimizely *OptimizelyClient
forcedDecisionService *pkgDecision.ForcedDecisionService
userProfile *pkgDecision.UserProfile
mutex *sync.RWMutex
}

Expand Down Expand Up @@ -130,7 +131,11 @@ func (o *OptimizelyUserContext) IsQualifiedFor(segment string) bool {
func (o *OptimizelyUserContext) Decide(key string, options []decide.OptimizelyDecideOptions) OptimizelyDecision {
// use a copy of the user context so that any changes to the original context are not reflected inside the decision
userContextCopy := newOptimizelyUserContext(o.GetOptimizely(), o.GetUserID(), o.GetUserAttributes(), o.getForcedDecisionService(), o.GetQualifiedSegments())
return o.optimizely.decide(userContextCopy, key, convertDecideOptions(options))
decision, found := o.optimizely.decideForKeys(userContextCopy, []string{key}, convertDecideOptions(options))[key]
if !found {
return NewErrorDecision(key, *o, decide.GetDecideError(decide.SDKNotReady))
}
return decision
}

// DecideAll returns a key-map of decision results for all active flag keys with options.
Expand Down
2 changes: 2 additions & 0 deletions pkg/decision/entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
type ExperimentDecisionContext struct {
Experiment *entities.Experiment
ProjectConfig config.ProjectConfig
UserProfile *UserProfile
}

// FeatureDecisionContext contains the information needed to be able to make a decision for a given feature
Expand All @@ -35,6 +36,7 @@ type FeatureDecisionContext struct {
ProjectConfig config.ProjectConfig
Variable entities.Variable
ForcedDecisionService *ForcedDecisionService
UserProfile *UserProfile
}

// UnsafeFeatureDecisionInfo represents response for GetDetailedFeatureDecisionUnsafe api
Expand Down
1 change: 1 addition & 0 deletions pkg/decision/feature_experiment_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ func (f FeatureExperimentService) GetDecision(decisionContext FeatureDecisionCon
experimentDecisionContext := ExperimentDecisionContext{
Experiment: &experiment,
ProjectConfig: decisionContext.ProjectConfig,
UserProfile: decisionContext.UserProfile,
}

experimentDecision, decisionReasons, err := f.compositeExperimentService.GetDecision(experimentDecisionContext, userContext, options)
Expand Down
20 changes: 13 additions & 7 deletions pkg/decision/persisting_experiment_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (p PersistingExperimentService) GetDecision(decisionContext ExperimentDecis
return p.experimentBucketedService.GetDecision(decisionContext, userContext, options)
}

var userProfile UserProfile
var userProfile *UserProfile
var decisionReasons decide.DecisionReasons
// check to see if there is a saved decision for the user
experimentDecision, userProfile, decisionReasons = p.getSavedDecision(decisionContext, userContext, options)
Expand All @@ -66,16 +66,20 @@ func (p PersistingExperimentService) GetDecision(decisionContext ExperimentDecis
if experimentDecision.Variation != nil {
// save decision if a user profile service is provided
userProfile.ID = userContext.ID
p.saveDecision(userProfile, decisionContext.Experiment, experimentDecision)
p.saveDecision(userProfile, decisionContext, experimentDecision)
}

return experimentDecision, reasons, err
}

func (p PersistingExperimentService) getSavedDecision(decisionContext ExperimentDecisionContext, userContext entities.UserContext, options *decide.Options) (ExperimentDecision, UserProfile, decide.DecisionReasons) {
func (p PersistingExperimentService) getSavedDecision(decisionContext ExperimentDecisionContext, userContext entities.UserContext, options *decide.Options) (ExperimentDecision, *UserProfile, decide.DecisionReasons) {
reasons := decide.NewDecisionReasons(options)
experimentDecision := ExperimentDecision{}
userProfile := p.userProfileService.Lookup(userContext.ID)
userProfile := decisionContext.UserProfile
if userProfile == nil {
up := p.userProfileService.Lookup(userContext.ID)
userProfile = &up
}

// look up experiment decision from user profile
decisionKey := NewUserDecisionKey(decisionContext.Experiment.ID)
Expand All @@ -97,14 +101,16 @@ func (p PersistingExperimentService) getSavedDecision(decisionContext Experiment
return experimentDecision, userProfile, reasons
}

func (p PersistingExperimentService) saveDecision(userProfile UserProfile, experiment *entities.Experiment, decision ExperimentDecision) {
func (p PersistingExperimentService) saveDecision(userProfile *UserProfile, decisionContext ExperimentDecisionContext, decision ExperimentDecision) {
if p.userProfileService != nil {
decisionKey := NewUserDecisionKey(experiment.ID)
decisionKey := NewUserDecisionKey(decisionContext.Experiment.ID)
if userProfile.ExperimentBucketMap == nil {
userProfile.ExperimentBucketMap = map[UserDecisionKey]string{}
}
userProfile.ExperimentBucketMap[decisionKey] = decision.Variation.ID
p.userProfileService.Save(userProfile)
if decisionContext.UserProfile == nil {
p.userProfileService.Save(*userProfile)
}
p.logger.Debug(fmt.Sprintf(`Decision saved for user %q.`, userProfile.ID))
}
}

0 comments on commit cfed748

Please sign in to comment.