Skip to content

Commit b27f1c6

Browse files
wip: decision service updated
1 parent 2bcfcd9 commit b27f1c6

File tree

1 file changed

+138
-12
lines changed

1 file changed

+138
-12
lines changed

Sources/Implementation/DefaultDecisionService.swift

Lines changed: 138 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ struct FeatureDecision {
2222
let source: String
2323
}
2424

25+
typealias UserProfile = OPTUserProfileService.UPProfile
26+
2527
class DefaultDecisionService: OPTDecisionService {
2628

2729
let bucketer: OPTBucketer
2830
let userProfileService: OPTUserProfileService
29-
31+
var profileTracker: UserProfileTracker?
3032
// thread-safe lazy logger load (after HandlerRegisterService ready)
3133
private let threadSafeLogger = ThreadSafeLogger()
3234
var logger: OPTLogger {
@@ -35,7 +37,7 @@ class DefaultDecisionService: OPTDecisionService {
3537

3638
// user-profile-service read-modify-write lock for supporting multiple clients
3739
static let upsRMWLock = DispatchQueue(label: "ups-rmw")
38-
40+
3941
init(userProfileService: OPTUserProfileService) {
4042
self.bucketer = DefaultBucketer()
4143
self.userProfileService = userProfileService
@@ -47,6 +49,73 @@ class DefaultDecisionService: OPTDecisionService {
4749
options: [OptimizelyDecideOption]? = nil) -> DecisionResponse<Variation> {
4850
let reasons = DecisionReasons(options: options)
4951

52+
let userId = user.userId
53+
//let attributes = user.attributes
54+
let experimentId = experiment.id
55+
let ignoreUPS = (options ?? []).contains(.ignoreUserProfileService)
56+
if !ignoreUPS {
57+
let profile = getUserProfile(userId: userId, reasons: reasons)
58+
profileTracker = UserProfileTracker(userProfile: profile, profileUpdated: false, userProfileService: self.userProfileService)
59+
}
60+
61+
let response = getVariation(config: config, experiment: experiment, user: user, _reasons: reasons)
62+
63+
let profileUpdated = profileTracker?.profileUpdated ?? false
64+
if (!ignoreUPS && profileUpdated) {
65+
saveProfile(profile: profileTracker?.userProfile)
66+
profileTracker = nil
67+
}
68+
69+
return response
70+
}
71+
72+
func getVariation(config: ProjectConfig,
73+
featureFlags: [FeatureFlag],
74+
user: OptimizelyUserContext,
75+
options: [OptimizelyDecideOption]? = nil) -> [DecisionResponse<FeatureDecision>] {
76+
let upsReasons = DecisionReasons(options: options)
77+
let userId = user.userId
78+
let ignoreUPS = (options ?? []).contains(.ignoreUserProfileService)
79+
if !ignoreUPS {
80+
let profile = getUserProfile(userId: userId, reasons: upsReasons)
81+
profileTracker = UserProfileTracker(userProfile: profile, profileUpdated: false, userProfileService: self.userProfileService)
82+
}
83+
84+
var decisions = [DecisionResponse<FeatureDecision>]()
85+
86+
for flag in featureFlags {
87+
var reasons = upsReasons
88+
let decisionVariationResponse = getVariationForFeatureExperiment(config: config, featureFlag: flag, user: user)
89+
reasons.merge(decisionVariationResponse.reasons)
90+
var decision = decisionVariationResponse.result
91+
if decision != nil {
92+
decisions.append(DecisionResponse(result: decision, reasons: reasons))
93+
continue
94+
}
95+
let decisionFeaturesResponse = getVariationForFeatureRollout(config: config, featureFlag: flag, user: user)
96+
reasons.merge(decisionFeaturesResponse.reasons)
97+
decision = decisionFeaturesResponse.result
98+
99+
decisions.append(DecisionResponse(result: decision, reasons: reasons))
100+
101+
}
102+
103+
// save profile
104+
let profileUpdated = profileTracker?.profileUpdated ?? false
105+
if !ignoreUPS && profileUpdated {
106+
saveProfile(profile: profileTracker?.userProfile)
107+
profileTracker = nil
108+
}
109+
110+
return decisions
111+
}
112+
113+
func getVariation(config: ProjectConfig,
114+
experiment: Experiment,
115+
user: OptimizelyUserContext,
116+
options: [OptimizelyDecideOption]? = nil,
117+
_reasons: DecisionReasons) -> DecisionResponse<Variation> {
118+
var reasons = _reasons
50119
let userId = user.userId
51120
let attributes = user.attributes
52121
let experimentId = experiment.id
@@ -85,19 +154,31 @@ class DefaultDecisionService: OPTDecisionService {
85154
reasons.addInfo(info)
86155
}
87156

88-
// ---- check if a valid variation is stored in the user profile ----
89-
let ignoreUPS = (options ?? []).contains(.ignoreUserProfileService)
90-
91-
if !ignoreUPS,
92-
let variationId = getVariationIdFromProfile(userId: userId, experimentId: experimentId),
157+
/// FIXME: Need to check the logic
158+
if let profile = self.profileTracker?.userProfile,
159+
let _userId = profile[UserProfileKeys.kUserId] as? String,
160+
let variationId = getVariationIdFromProfile(userId: _userId, experimentId: experimentId),
93161
let variation = experiment.getVariation(id: variationId) {
94-
162+
95163
let info = LogMessage.gotVariationFromUserProfile(variation.key, experiment.key, userId)
96164
logger.i(info)
97165
reasons.addInfo(info)
98166
return DecisionResponse(result: variation, reasons: reasons)
99167
}
100168

169+
// // ---- check if a valid variation is stored in the user profile ----
170+
// let ignoreUPS = (options ?? []).contains(.ignoreUserProfileService)
171+
//
172+
// if !ignoreUPS,
173+
// let variationId = getVariationIdFromProfile(userId: userId, experimentId: experimentId),
174+
// let variation = experiment.getVariation(id: variationId) {
175+
//
176+
// let info = LogMessage.gotVariationFromUserProfile(variation.key, experiment.key, userId)
177+
// logger.i(info)
178+
// reasons.addInfo(info)
179+
// return DecisionResponse(result: variation, reasons: reasons)
180+
// }
181+
//
101182
var bucketedVariation: Variation?
102183
// ---- check if the user passes audience targeting before bucketing ----
103184
let audienceResponse = doesMeetAudienceConditions(config: config,
@@ -116,10 +197,7 @@ class DefaultDecisionService: OPTDecisionService {
116197
let info = LogMessage.userBucketedIntoVariationInExperiment(userId, experiment.key, variation.key)
117198
logger.i(info)
118199
reasons.addInfo(info)
119-
// save to user profile
120-
if !ignoreUPS {
121-
self.saveProfile(userId: userId, experimentId: experimentId, variationId: variation.id)
122-
}
200+
self.profileTracker?.updateProfile(experiment: experiment, variation: variation)
123201
} else {
124202
let info = LogMessage.userNotBucketedIntoVariation(userId)
125203
logger.i(info)
@@ -452,6 +530,22 @@ class DefaultDecisionService: OPTDecisionService {
452530
// MARK: - UserProfileService Helpers
453531

454532
extension DefaultDecisionService {
533+
// fixme: Need to cross check the logic
534+
private func getUserProfile(userId: String, reasons: DecisionReasons) -> UserProfile {
535+
var userProfile: UserProfile?
536+
let lookUpUserProfile = userProfileService.lookup(userId: userId)
537+
if lookUpUserProfile != nil {
538+
userProfile = lookUpUserProfile
539+
} else {
540+
logger.w("The user profile service return nil for userId: \(userId)")
541+
}
542+
543+
if userProfile == nil {
544+
userProfile = [String: Any]()
545+
}
546+
547+
return userProfile!
548+
}
455549

456550
func getVariationIdFromProfile(userId: String,
457551
experimentId: String) -> String? {
@@ -483,4 +577,36 @@ extension DefaultDecisionService {
483577
}
484578
}
485579

580+
func saveProfile(profile: UserProfile?) {
581+
DefaultDecisionService.upsRMWLock.sync {
582+
guard let profile = profile else { return }
583+
self.userProfileService.save(userProfile: profile)
584+
}
585+
}
586+
}
587+
588+
class UserProfileTracker {
589+
var userProfile: UserProfile
590+
var profileUpdated: Bool
591+
var userProfileService: OPTUserProfileService
592+
593+
init(userProfile: UserProfile, profileUpdated: Bool, userProfileService: OPTUserProfileService) {
594+
self.userProfile = userProfile
595+
self.profileUpdated = profileUpdated
596+
self.userProfileService = userProfileService
597+
}
598+
599+
func updateProfile(experiment: Experiment, variation: Variation) {
600+
let experimentId = experiment.id
601+
let variationId = variation.id
602+
var bucketMap = userProfile[UserProfileKeys.kBucketMap] as? OPTUserProfileService.UPBucketMap ?? OPTUserProfileService.UPBucketMap()
603+
bucketMap[experimentId] = [UserProfileKeys.kVariationId: variationId]
604+
userProfile[UserProfileKeys.kBucketMap] = bucketMap
605+
// profile[UserProfileKeys.kUserId] = userId
606+
607+
}
608+
609+
func save() {
610+
userProfileService.save(userProfile: userProfile)
611+
}
486612
}

0 commit comments

Comments
 (0)