Skip to content

Commit 19f4580

Browse files
WIP: UPS batch update draft changes
1 parent 2bcfcd9 commit 19f4580

File tree

3 files changed

+64
-22
lines changed

3 files changed

+64
-22
lines changed

Sources/Implementation/DefaultDecisionService.swift

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,26 @@ struct FeatureDecision {
2323
}
2424

2525
class DefaultDecisionService: OPTDecisionService {
26+
typealias UserProfile = OPTUserProfileService.UPProfile
2627

28+
private var _decisionBatchInProgress: Bool = false
29+
30+
var decisionBatchInProgress: Bool {
31+
get {
32+
return _decisionBatchInProgress
33+
}
34+
set {
35+
// Only save if the value is changing from true to false
36+
if _decisionBatchInProgress && !newValue {
37+
saveProfile()
38+
}
39+
_decisionBatchInProgress = newValue
40+
}
41+
}
42+
2743
let bucketer: OPTBucketer
2844
let userProfileService: OPTUserProfileService
45+
private var userProfile: UserProfile?
2946

3047
// thread-safe lazy logger load (after HandlerRegisterService ready)
3148
private let threadSafeLogger = ThreadSafeLogger()
@@ -88,16 +105,26 @@ class DefaultDecisionService: OPTDecisionService {
88105
// ---- check if a valid variation is stored in the user profile ----
89106
let ignoreUPS = (options ?? []).contains(.ignoreUserProfileService)
90107

91-
if !ignoreUPS,
92-
let variationId = getVariationIdFromProfile(userId: userId, experimentId: experimentId),
93-
let variation = experiment.getVariation(id: variationId) {
108+
if !ignoreUPS {
109+
if userProfile == nil {
110+
userProfile = userProfileService.lookup(userId: userId)
111+
}
112+
113+
if let profile = userProfile {
114+
if let variationId = getVariationIdFromProfile(userId: userId, profile: profile, experimentId: experimentId),
115+
let variation = experiment.getVariation(id: variationId) {
116+
let info = LogMessage.gotVariationFromUserProfile(variation.key, experiment.key, userId)
117+
logger.i(info)
118+
reasons.addInfo(info)
119+
return DecisionResponse(result: variation, reasons: reasons)
120+
}
121+
} else {
122+
let info = LogMessage.unableToGetUserProfile(experiment.key, userId)
123+
logger.i(info)
124+
}
94125

95-
let info = LogMessage.gotVariationFromUserProfile(variation.key, experiment.key, userId)
96-
logger.i(info)
97-
reasons.addInfo(info)
98-
return DecisionResponse(result: variation, reasons: reasons)
99126
}
100-
127+
101128
var bucketedVariation: Variation?
102129
// ---- check if the user passes audience targeting before bucketing ----
103130
let audienceResponse = doesMeetAudienceConditions(config: config,
@@ -118,7 +145,8 @@ class DefaultDecisionService: OPTDecisionService {
118145
reasons.addInfo(info)
119146
// save to user profile
120147
if !ignoreUPS {
121-
self.saveProfile(userId: userId, experimentId: experimentId, variationId: variation.id)
148+
let buckerUserProfile = userProfile ?? UserProfile()
149+
updateVariation(userId: userId, profile: buckerUserProfile, experimentId: experimentId, variationId: variation.key)
122150
}
123151
} else {
124152
let info = LogMessage.userNotBucketedIntoVariation(userId)
@@ -454,9 +482,9 @@ class DefaultDecisionService: OPTDecisionService {
454482
extension DefaultDecisionService {
455483

456484
func getVariationIdFromProfile(userId: String,
485+
profile: UserProfile,
457486
experimentId: String) -> String? {
458-
if let profile = userProfileService.lookup(userId: userId),
459-
let bucketMap = profile[UserProfileKeys.kBucketMap] as? OPTUserProfileService.UPBucketMap,
487+
if let bucketMap = profile[UserProfileKeys.kBucketMap] as? OPTUserProfileService.UPBucketMap,
460488
let experimentMap = bucketMap[experimentId],
461489
let variationId = experimentMap[UserProfileKeys.kVariationId] {
462490
return variationId
@@ -465,22 +493,33 @@ extension DefaultDecisionService {
465493
}
466494
}
467495

468-
func saveProfile(userId: String,
469-
experimentId: String,
470-
variationId: String) {
496+
func updateVariation(userId: String,
497+
profile: UserProfile,
498+
experimentId: String,
499+
variationId: String) {
471500
DefaultDecisionService.upsRMWLock.sync {
472-
var profile = self.userProfileService.lookup(userId: userId) ?? OPTUserProfileService.UPProfile()
473-
474-
var bucketMap = profile[UserProfileKeys.kBucketMap] as? OPTUserProfileService.UPBucketMap ?? OPTUserProfileService.UPBucketMap()
501+
var _profile = profile
502+
var bucketMap = _profile[UserProfileKeys.kBucketMap] as? OPTUserProfileService.UPBucketMap ?? OPTUserProfileService.UPBucketMap()
475503
bucketMap[experimentId] = [UserProfileKeys.kVariationId: variationId]
476504

477-
profile[UserProfileKeys.kBucketMap] = bucketMap
478-
profile[UserProfileKeys.kUserId] = userId
505+
_profile[UserProfileKeys.kBucketMap] = bucketMap
506+
_profile[UserProfileKeys.kUserId] = userId
479507

480-
self.userProfileService.save(userProfile: profile)
508+
/// Update user profile
509+
userProfile = _profile
481510

482-
self.logger.i(.savedVariationInUserProfile(variationId, experimentId, userId))
511+
if !_decisionBatchInProgress {
512+
saveProfile(userId: userId, experimentId: experimentId, variationId: variationId)
513+
}
483514
}
484515
}
485516

517+
func saveProfile(userId: String? = nil, experimentId: String? = nil, variationId: String? = nil) {
518+
guard let profile = userProfile else { return }
519+
520+
self.userProfileService.save(userProfile: profile)
521+
522+
self.logger.i(.savedVariationInUserProfile(variationId ?? "", experimentId ?? "", userId ?? ""))
523+
}
524+
486525
}

Sources/Optimizely+Decide/OptimizelyClient+Decide.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,14 @@ extension OptimizelyClient {
173173
var decisions = [String: OptimizelyDecision]()
174174

175175
let enabledFlagsOnly = allOptions.contains(.enabledFlagsOnly)
176+
(decisionService as? DefaultDecisionService)?.decisionBatchInProgress = true
176177
keys.forEach { key in
177178
let decision = decide(user: user, key: key, options: options)
178179
if !enabledFlagsOnly || decision.enabled {
179180
decisions[key] = decision
180181
}
181182
}
182-
183+
(decisionService as? DefaultDecisionService)?.decisionBatchInProgress = false
183184
return decisions
184185
}
185186

Sources/Utils/LogMessage.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ enum LogMessage {
2727
case failedToExtractRevenueFromEventTags(_ val: String)
2828
case failedToExtractValueFromEventTags(_ val: String)
2929
case gotVariationFromUserProfile(_ varKey: String, _ expKey: String, _ userId: String)
30+
case unableToGetUserProfile(_ expKey: String, _ userId: String)
3031
case rolloutHasNoExperiments(_ id: String)
3132
case forcedVariationFound(_ key: String, _ userId: String)
3233
case forcedVariationFoundButInvalid(_ key: String, _ userId: String)
@@ -85,6 +86,7 @@ extension LogMessage: CustomStringConvertible {
8586
case .failedToExtractRevenueFromEventTags(let val): message = "Failed to parse revenue (\(val)) from event tags."
8687
case .failedToExtractValueFromEventTags(let val): message = "Failed to parse value (\(val)) from event tags."
8788
case .gotVariationFromUserProfile(let varKey, let expKey, let userId): message = "Returning previously activated variation (\(varKey)) of experiment (\(expKey)) for user (\(userId)) from user profile."
89+
case .unableToGetUserProfile(let expKey, let userId): message = "Unable to get user profile for user (\(userId))."
8890
case .rolloutHasNoExperiments(let id): message = "Rollout of feature (\(id)) has no experiments"
8991
case .forcedVariationFound(let key, let userId): message = "Forced variation (\(key)) is found for user (\(userId))"
9092
case .forcedVariationFoundButInvalid(let key, let userId): message = "Forced variation (\(key)) is found for user (\(userId)), but it's not in datafile."

0 commit comments

Comments
 (0)