@@ -69,8 +69,9 @@ import {
69
69
} from 'log_message' ;
70
70
import { OptimizelyError } from '../../error/optimizly_error' ;
71
71
import { CmabService } from './cmab/cmab_service' ;
72
- import { OpType , OpValue } from '../../utils/type' ;
73
- import { opValue , opThen } from '../../utils/promise/opValue' ;
72
+ import { Maybe , OpType , OpValue } from '../../utils/type' ;
73
+ import { opValue , opThen , opAll } from '../../utils/promise/opValue' ;
74
+ import { resolve } from 'path' ;
74
75
75
76
export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.' ;
76
77
export const RETURNING_STORED_VARIATION =
@@ -132,6 +133,7 @@ interface UserProfileTracker {
132
133
133
134
type DecisionReason = [ string , ...any [ ] ] ;
134
135
type VariationResult = DecisionResponse < string | null > ;
136
+
135
137
type DecisionResult = DecisionResponse < DecisionObj > ;
136
138
137
139
/**
@@ -177,14 +179,13 @@ export class DecisionService {
177
179
* @returns {DecisionResponse<string|null> } - A DecisionResponse containing the variation the user is bucketed into,
178
180
* along with the decision reasons.
179
181
*/
180
- private resolveVariation < O extends OpType > (
182
+ private resolveVariation < OP extends OpType > (
183
+ op : OP ,
181
184
configObj : ProjectConfig ,
182
185
experiment : Experiment ,
183
186
user : OptimizelyUserContext ,
184
- shouldIgnoreUPS : boolean ,
185
- userProfileTracker : UserProfileTracker ,
186
- op : O
187
- ) : OpValue < O , DecisionResponse < string | null > > {
187
+ userProfileTracker ?: UserProfileTracker ,
188
+ ) : OpValue < OP , DecisionResponse < string | null > > {
188
189
const userId = user . getUserId ( ) ;
189
190
const attributes = user . getAttributes ( ) ;
190
191
@@ -224,8 +225,8 @@ export class DecisionService {
224
225
} ) ;
225
226
}
226
227
227
- // check for sticky bucketing if decide options do not include shouldIgnoreUPS
228
- if ( ! shouldIgnoreUPS ) {
228
+ // check for sticky bucketing
229
+ if ( userProfileTracker ) {
229
230
variation = this . getStoredVariation ( configObj , experiment , userId , userProfileTracker . userProfile ) ;
230
231
if ( variation ) {
231
232
this . logger ?. info (
@@ -311,7 +312,7 @@ export class DecisionService {
311
312
experimentKey ,
312
313
] ) ;
313
314
// persist bucketing if decide options do not include shouldIgnoreUPS
314
- if ( ! shouldIgnoreUPS ) {
315
+ if ( userProfileTracker ) {
315
316
this . updateUserProfile ( experiment , variation , userProfileTracker ) ;
316
317
}
317
318
@@ -425,15 +426,24 @@ export class DecisionService {
425
426
* @param {UserAttributes } attributes
426
427
* @return {ExperimentBucketMap } finalized copy of experiment_bucket_map
427
428
*/
428
- private resolveExperimentBucketMap (
429
+ private resolveExperimentBucketMap < O extends OpType > (
430
+ op : O ,
429
431
userId : string ,
430
- attributes ?: UserAttributes
431
- ) : ExperimentBucketMap {
432
+ attributes ?: UserAttributes ,
433
+ ) : OpValue < O , ExperimentBucketMap > {
432
434
attributes = attributes || { } ;
433
435
434
- const userProfile = this . getUserProfile ( userId ) || { } as UserProfile ;
435
- const attributeExperimentBucketMap = attributes [ CONTROL_ATTRIBUTES . STICKY_BUCKETING_KEY ] ;
436
- return { ...userProfile . experiment_bucket_map , ...attributeExperimentBucketMap as any } ;
436
+ return opThen ( op , this . getUserProfile ( userId , op ) , ( userProfile ) => {
437
+ const experimentBucketMap = {
438
+ ...userProfile ?. experiment_bucket_map ,
439
+ ...attributes [ CONTROL_ATTRIBUTES . STICKY_BUCKETING_KEY ] as any || { } ,
440
+ }
441
+ return opValue ( op , experimentBucketMap ) ;
442
+ } ) ;
443
+
444
+ // const userProfile = this.getUserProfile(userId) || {} as UserProfile;
445
+ // const attributeExperimentBucketMap = attributes[CONTROL_ATTRIBUTES.STICKY_BUCKETING_KEY];
446
+ // return { ...userProfile.experiment_bucket_map, ...attributeExperimentBucketMap as any };
437
447
}
438
448
439
449
/**
@@ -605,20 +615,15 @@ export class DecisionService {
605
615
/**
606
616
* Get the user profile with the given user ID
607
617
* @param {string } userId
608
- * @return {UserProfile|null } the stored user profile or null if one isn't found
618
+ * @return {UserProfile|null } the stored user profile or undefined if one isn't found
609
619
*/
610
- private getUserProfile ( userId : string ) : UserProfile | null {
611
- const userProfile = {
612
- user_id : userId ,
613
- experiment_bucket_map : { } ,
614
- } ;
615
-
616
- if ( ! this . userProfileService ) {
617
- return userProfile ;
620
+ private getUserProfile < O extends OpType > ( userId : string , op : O ) : OpValue < O , Maybe < UserProfile > > {
621
+ if ( ! this . userProfileService || op === 'sync' ) {
622
+ return opValue ( op , undefined ) ;
618
623
}
619
624
620
625
try {
621
- return this . userProfileService . lookup ( userId ) ;
626
+ return this . userProfileService . lookup ( userId ) as any ;
622
627
} catch ( ex : any ) {
623
628
this . logger ?. error (
624
629
USER_PROFILE_LOOKUP_ERROR ,
@@ -627,7 +632,7 @@ export class DecisionService {
627
632
) ;
628
633
}
629
634
630
- return null ;
635
+ return opValue ( op , undefined ) ;
631
636
}
632
637
633
638
private updateUserProfile (
@@ -677,6 +682,7 @@ export class DecisionService {
677
682
}
678
683
}
679
684
685
+
680
686
/**
681
687
* Determines variations for the specified feature flags.
682
688
*
@@ -687,62 +693,145 @@ export class DecisionService {
687
693
* @returns {DecisionResponse<DecisionObj>[] } - An array of DecisionResponse containing objects with
688
694
* experiment, variation, decisionSource properties, and decision reasons.
689
695
*/
690
- getVariationsForFeatureList ( configObj : ProjectConfig ,
696
+ getVariationsForFeatureList < OP extends OpType > (
697
+ op : OP ,
698
+ configObj : ProjectConfig ,
691
699
featureFlags : FeatureFlag [ ] ,
692
700
user : OptimizelyUserContext ,
693
- options : { [ key : string ] : boolean } = { } ) : DecisionResponse < DecisionObj > [ ] {
701
+ options : { [ key : string ] : boolean } = { } ) : OpValue < OP , DecisionResult [ ] > {
694
702
const userId = user . getUserId ( ) ;
695
703
const attributes = user . getAttributes ( ) ;
696
704
const decisions : DecisionResponse < DecisionObj > [ ] = [ ] ;
697
- const userProfileTracker : UserProfileTracker = {
698
- isProfileUpdated : false ,
699
- userProfile : null ,
700
- }
705
+ // const userProfileTracker : UserProfileTracker = {
706
+ // isProfileUpdated: false,
707
+ // userProfile: null,
708
+ // }
701
709
const shouldIgnoreUPS = ! ! options [ OptimizelyDecideOption . IGNORE_USER_PROFILE_SERVICE ] ;
702
710
703
- if ( ! shouldIgnoreUPS ) {
704
- userProfileTracker . userProfile = this . resolveExperimentBucketMap ( userId , attributes ) ;
705
- }
711
+ const opUserProfileTracker : OpValue < OP , Maybe < UserProfileTracker > > = shouldIgnoreUPS ? opValue ( op , undefined )
712
+ : opThen ( op , this . resolveExperimentBucketMap ( op , userId , attributes ) , ( userProfile ) => {
713
+ return opValue ( op , {
714
+ isProfileUpdated : false ,
715
+ userProfile : userProfile ,
716
+ } ) ;
717
+ } ) ;
706
718
707
- for ( const feature of featureFlags ) {
708
- const decideReasons : ( string | number ) [ ] [ ] = [ ] ;
709
- const decisionVariation = this . getVariationForFeatureExperiment ( configObj , feature , user , shouldIgnoreUPS , userProfileTracker ) ;
710
- decideReasons . push ( ...decisionVariation . reasons ) ;
711
- const experimentDecision = decisionVariation . result ;
719
+ // if(!shouldIgnoreUPS) {
720
+ // // userProfileTracker = opThen(op, this.resolveExperimentBucketMap(op, userId, attributes), (userProfile) => {
721
+ // // return opValue(op, {
722
+ // // isProfileUpdated: false,
723
+ // // userProfile: userProfile,
724
+ // // });
725
+ // // });
726
+ // // optThen
727
+ // // userProfileTracker.userProfile = this.resolveExperimentBucketMap(userId, attributes);
728
+ // }
712
729
713
- if ( experimentDecision . variation !== null ) {
714
- decisions . push ( {
715
- result : experimentDecision ,
716
- reasons : decideReasons ,
717
- } ) ;
718
- continue ;
730
+ return opThen ( op , opUserProfileTracker , ( userProfileTracker ) => {
731
+ const flagResults = featureFlags . map ( ( feature ) => this . resolveVariationForFlag ( op , configObj , feature , user , userProfileTracker ) ) ;
732
+ const opFlagResults = opAll ( op , flagResults ) ;
733
+
734
+ return opThen ( op , opFlagResults , ( ) => {
735
+ if ( userProfileTracker ) {
736
+ this . saveUserProfile ( userId , userProfileTracker ) ;
737
+ }
738
+ return opFlagResults ;
739
+ } ) ;
740
+ } ) ;
741
+
742
+ // return opThen(op, opFlagResults, (flagResults) => {
743
+ // if(!shouldIgnoreUPS) {
744
+ // this.saveUserProfile(userId, flagResults);
745
+ // }
746
+
747
+ // return opFlagResults;
748
+ // }
749
+
750
+ // for(const feature of featureFlags) {
751
+ // const decideReasons: (string | number)[][] = [];
752
+ // const decisionVariation = this.getVariationForFeatureExperiment(configObj, feature, user, shouldIgnoreUPS, userProfileTracker);
753
+ // decideReasons.push(...decisionVariation.reasons);
754
+ // const experimentDecision = decisionVariation.result;
755
+
756
+ // if (experimentDecision.variation !== null) {
757
+ // decisions.push({
758
+ // result: experimentDecision,
759
+ // reasons: decideReasons,
760
+ // });
761
+ // continue;
762
+ // }
763
+
764
+ // const decisionRolloutVariation = this.getVariationForRollout(configObj, feature, user);
765
+ // decideReasons.push(...decisionRolloutVariation.reasons);
766
+ // const rolloutDecision = decisionRolloutVariation.result;
767
+ // const userId = user.getUserId();
768
+
769
+ // if (rolloutDecision.variation) {
770
+ // this.logger?.debug(USER_IN_ROLLOUT, userId, feature.key);
771
+ // decideReasons.push([USER_IN_ROLLOUT, userId, feature.key]);
772
+ // } else {
773
+ // this.logger?.debug(USER_NOT_IN_ROLLOUT, userId, feature.key);
774
+ // decideReasons.push([USER_NOT_IN_ROLLOUT, userId, feature.key]);
775
+ // }
776
+
777
+ // decisions.push({
778
+ // result: rolloutDecision,
779
+ // reasons: decideReasons,
780
+ // });
781
+ // }
782
+
783
+ // if(!shouldIgnoreUPS) {
784
+ // this.saveUserProfile(userId, userProfileTracker);
785
+ // }
786
+
787
+ // return decisions;
788
+ }
789
+
790
+ private resolveVariationForFlag < O extends OpType > (
791
+ op : O ,
792
+ configObj : ProjectConfig ,
793
+ feature : FeatureFlag ,
794
+ user : OptimizelyUserContext ,
795
+ userProfileTracker ?: UserProfileTracker
796
+ ) : OpValue < O , DecisionResult > {
797
+ const forcedDecisionResponse = this . findValidatedForcedDecision ( configObj , user , feature . key ) ;
798
+
799
+ if ( forcedDecisionResponse . result ) {
800
+ return opValue ( op , {
801
+ result : {
802
+ variation : forcedDecisionResponse . result ,
803
+ experiment : null ,
804
+ decisionSource : DECISION_SOURCES . FEATURE_TEST ,
805
+ } ,
806
+ reasons : forcedDecisionResponse . reasons ,
807
+ } ) ;
808
+ }
809
+
810
+ return opThen ( op , this . getVariationForFeatureExperiment ( op , configObj , feature , user , userProfileTracker ) , ( experimentDecision ) => {
811
+ if ( experimentDecision . error || experimentDecision . result . variation !== null ) {
812
+ return opValue ( op , experimentDecision ) ;
719
813
}
720
814
721
- const decisionRolloutVariation = this . getVariationForRollout ( configObj , feature , user ) ;
722
- decideReasons . push ( ...decisionRolloutVariation . reasons ) ;
723
- const rolloutDecision = decisionRolloutVariation . result ;
815
+ const decideReasons = experimentDecision . reasons ;
816
+
817
+ const rolloutDecision = this . getVariationForRollout ( configObj , feature , user ) ;
818
+ decideReasons . push ( ...rolloutDecision . reasons ) ;
819
+ const rolloutDecisionResult = rolloutDecision . result ;
724
820
const userId = user . getUserId ( ) ;
725
-
726
- if ( rolloutDecision . variation ) {
821
+
822
+ if ( rolloutDecisionResult . variation ) {
727
823
this . logger ?. debug ( USER_IN_ROLLOUT , userId , feature . key ) ;
728
824
decideReasons . push ( [ USER_IN_ROLLOUT , userId , feature . key ] ) ;
729
825
} else {
730
826
this . logger ?. debug ( USER_NOT_IN_ROLLOUT , userId , feature . key ) ;
731
827
decideReasons . push ( [ USER_NOT_IN_ROLLOUT , userId , feature . key ] ) ;
732
828
}
733
-
734
- decisions . push ( {
735
- result : rolloutDecision ,
829
+
830
+ return opValue ( op , {
831
+ result : rolloutDecisionResult ,
736
832
reasons : decideReasons ,
737
833
} ) ;
738
- }
739
-
740
- if ( ! shouldIgnoreUPS ) {
741
- this . saveUserProfile ( userId , userProfileTracker ) ;
742
- }
743
-
744
- return decisions ;
745
-
834
+ } ) ;
746
835
}
747
836
748
837
/**
@@ -766,16 +855,15 @@ export class DecisionService {
766
855
user : OptimizelyUserContext ,
767
856
options : { [ key : string ] : boolean } = { }
768
857
) : DecisionResponse < DecisionObj > {
769
- return this . getVariationsForFeatureList ( configObj , [ feature ] , user , options ) [ 0 ]
858
+ return this . getVariationsForFeatureList ( 'sync' , configObj , [ feature ] , user , options ) [ 0 ]
770
859
}
771
860
772
861
private getVariationForFeatureExperiment < O extends OpType > (
862
+ op : O ,
773
863
configObj : ProjectConfig ,
774
864
feature : FeatureFlag ,
775
865
user : OptimizelyUserContext ,
776
- shouldIgnoreUPS : boolean ,
777
- userProfileTracker : UserProfileTracker ,
778
- op : O ,
866
+ userProfileTracker ?: UserProfileTracker ,
779
867
) : OpValue < O , DecisionResult > {
780
868
781
869
// const decideReasons: (string | number)[][] = [];
@@ -810,7 +898,7 @@ export class DecisionService {
810
898
}
811
899
812
900
const decisionVariation = this . getVariationFromExperimentRule (
813
- configObj , feature . key , experiment , user , shouldIgnoreUPS , userProfileTracker , op
901
+ op , configObj , feature . key , experiment , user , userProfileTracker ,
814
902
) ;
815
903
816
904
return opThen ( op , decisionVariation , ( decisionVariation ) => {
@@ -1309,13 +1397,12 @@ export class DecisionService {
1309
1397
}
1310
1398
1311
1399
private getVariationFromExperimentRule < O extends OpType > (
1400
+ op : O ,
1312
1401
configObj : ProjectConfig ,
1313
1402
flagKey : string ,
1314
1403
rule : Experiment ,
1315
1404
user : OptimizelyUserContext ,
1316
- shouldIgnoreUPS : boolean ,
1317
- userProfileTracker : UserProfileTracker ,
1318
- op : O ,
1405
+ userProfileTracker ?: UserProfileTracker ,
1319
1406
) : OpValue < O , VariationResult > {
1320
1407
const decideReasons : ( string | number ) [ ] [ ] = [ ] ;
1321
1408
@@ -1330,7 +1417,7 @@ export class DecisionService {
1330
1417
reasons : decideReasons ,
1331
1418
} ) ;
1332
1419
}
1333
- const decisionVariation = this . resolveVariation ( configObj , rule , user , shouldIgnoreUPS , userProfileTracker , op ) ;
1420
+ const decisionVariation = this . resolveVariation ( op , configObj , rule , user , userProfileTracker ) ;
1334
1421
1335
1422
const response = opThen ( op , decisionVariation , ( variationResult ) => {
1336
1423
decideReasons . push ( ...variationResult . reasons ) ;
0 commit comments