@@ -22,11 +22,13 @@ struct FeatureDecision {
22
22
let source : String
23
23
}
24
24
25
+ typealias UserProfile = OPTUserProfileService . UPProfile
26
+
25
27
class DefaultDecisionService : OPTDecisionService {
26
28
27
29
let bucketer : OPTBucketer
28
30
let userProfileService : OPTUserProfileService
29
-
31
+ var profileTracker : UserProfileTracker ?
30
32
// thread-safe lazy logger load (after HandlerRegisterService ready)
31
33
private let threadSafeLogger = ThreadSafeLogger ( )
32
34
var logger : OPTLogger {
@@ -35,7 +37,7 @@ class DefaultDecisionService: OPTDecisionService {
35
37
36
38
// user-profile-service read-modify-write lock for supporting multiple clients
37
39
static let upsRMWLock = DispatchQueue ( label: " ups-rmw " )
38
-
40
+
39
41
init ( userProfileService: OPTUserProfileService ) {
40
42
self . bucketer = DefaultBucketer ( )
41
43
self . userProfileService = userProfileService
@@ -47,6 +49,73 @@ class DefaultDecisionService: OPTDecisionService {
47
49
options: [ OptimizelyDecideOption ] ? = nil ) -> DecisionResponse < Variation > {
48
50
let reasons = DecisionReasons ( options: options)
49
51
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
50
119
let userId = user. userId
51
120
let attributes = user. attributes
52
121
let experimentId = experiment. id
@@ -85,19 +154,31 @@ class DefaultDecisionService: OPTDecisionService {
85
154
reasons. addInfo ( info)
86
155
}
87
156
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) ,
93
161
let variation = experiment. getVariation ( id: variationId) {
94
-
162
+
95
163
let info = LogMessage . gotVariationFromUserProfile ( variation. key, experiment. key, userId)
96
164
logger. i ( info)
97
165
reasons. addInfo ( info)
98
166
return DecisionResponse ( result: variation, reasons: reasons)
99
167
}
100
168
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
+ //
101
182
var bucketedVariation : Variation ?
102
183
// ---- check if the user passes audience targeting before bucketing ----
103
184
let audienceResponse = doesMeetAudienceConditions ( config: config,
@@ -116,10 +197,7 @@ class DefaultDecisionService: OPTDecisionService {
116
197
let info = LogMessage . userBucketedIntoVariationInExperiment ( userId, experiment. key, variation. key)
117
198
logger. i ( info)
118
199
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)
123
201
} else {
124
202
let info = LogMessage . userNotBucketedIntoVariation ( userId)
125
203
logger. i ( info)
@@ -452,6 +530,22 @@ class DefaultDecisionService: OPTDecisionService {
452
530
// MARK: - UserProfileService Helpers
453
531
454
532
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
+ }
455
549
456
550
func getVariationIdFromProfile( userId: String ,
457
551
experimentId: String ) -> String ? {
@@ -483,4 +577,36 @@ extension DefaultDecisionService {
483
577
}
484
578
}
485
579
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
+ }
486
612
}
0 commit comments