Skip to content

Commit 6b447a2

Browse files
committed
update
1 parent aadf6e8 commit 6b447a2

File tree

6 files changed

+344
-78
lines changed

6 files changed

+344
-78
lines changed

lib/core/decision_service/index.ts

Lines changed: 159 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ import {
6969
} from 'log_message';
7070
import { OptimizelyError } from '../../error/optimizly_error';
7171
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';
7475

7576
export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.';
7677
export const RETURNING_STORED_VARIATION =
@@ -132,6 +133,7 @@ interface UserProfileTracker {
132133

133134
type DecisionReason = [string, ...any[]];
134135
type VariationResult = DecisionResponse<string | null>;
136+
135137
type DecisionResult = DecisionResponse<DecisionObj>;
136138

137139
/**
@@ -177,14 +179,13 @@ export class DecisionService {
177179
* @returns {DecisionResponse<string|null>} - A DecisionResponse containing the variation the user is bucketed into,
178180
* along with the decision reasons.
179181
*/
180-
private resolveVariation<O extends OpType>(
182+
private resolveVariation<OP extends OpType>(
183+
op: OP,
181184
configObj: ProjectConfig,
182185
experiment: Experiment,
183186
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>> {
188189
const userId = user.getUserId();
189190
const attributes = user.getAttributes();
190191

@@ -224,8 +225,8 @@ export class DecisionService {
224225
});
225226
}
226227

227-
// check for sticky bucketing if decide options do not include shouldIgnoreUPS
228-
if (!shouldIgnoreUPS) {
228+
// check for sticky bucketing
229+
if (userProfileTracker) {
229230
variation = this.getStoredVariation(configObj, experiment, userId, userProfileTracker.userProfile);
230231
if (variation) {
231232
this.logger?.info(
@@ -311,7 +312,7 @@ export class DecisionService {
311312
experimentKey,
312313
]);
313314
// persist bucketing if decide options do not include shouldIgnoreUPS
314-
if (!shouldIgnoreUPS) {
315+
if (userProfileTracker) {
315316
this.updateUserProfile(experiment, variation, userProfileTracker);
316317
}
317318

@@ -425,15 +426,24 @@ export class DecisionService {
425426
* @param {UserAttributes} attributes
426427
* @return {ExperimentBucketMap} finalized copy of experiment_bucket_map
427428
*/
428-
private resolveExperimentBucketMap(
429+
private resolveExperimentBucketMap<O extends OpType>(
430+
op: O,
429431
userId: string,
430-
attributes?: UserAttributes
431-
): ExperimentBucketMap {
432+
attributes?: UserAttributes,
433+
): OpValue<O, ExperimentBucketMap> {
432434
attributes = attributes || {};
433435

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 };
437447
}
438448

439449
/**
@@ -605,20 +615,15 @@ export class DecisionService {
605615
/**
606616
* Get the user profile with the given user ID
607617
* @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
609619
*/
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);
618623
}
619624

620625
try {
621-
return this.userProfileService.lookup(userId);
626+
return this.userProfileService.lookup(userId) as any;
622627
} catch (ex: any) {
623628
this.logger?.error(
624629
USER_PROFILE_LOOKUP_ERROR,
@@ -627,7 +632,7 @@ export class DecisionService {
627632
);
628633
}
629634

630-
return null;
635+
return opValue(op, undefined);
631636
}
632637

633638
private updateUserProfile(
@@ -677,6 +682,7 @@ export class DecisionService {
677682
}
678683
}
679684

685+
680686
/**
681687
* Determines variations for the specified feature flags.
682688
*
@@ -687,62 +693,145 @@ export class DecisionService {
687693
* @returns {DecisionResponse<DecisionObj>[]} - An array of DecisionResponse containing objects with
688694
* experiment, variation, decisionSource properties, and decision reasons.
689695
*/
690-
getVariationsForFeatureList(configObj: ProjectConfig,
696+
getVariationsForFeatureList<OP extends OpType>(
697+
op: OP,
698+
configObj: ProjectConfig,
691699
featureFlags: FeatureFlag[],
692700
user: OptimizelyUserContext,
693-
options: { [key: string]: boolean } = {}): DecisionResponse<DecisionObj>[] {
701+
options: { [key: string]: boolean } = {}): OpValue<OP, DecisionResult[]> {
694702
const userId = user.getUserId();
695703
const attributes = user.getAttributes();
696704
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+
// }
701709
const shouldIgnoreUPS = !!options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE];
702710

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+
});
706718

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+
// }
712729

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);
719813
}
720814

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;
724820
const userId = user.getUserId();
725-
726-
if (rolloutDecision.variation) {
821+
822+
if (rolloutDecisionResult.variation) {
727823
this.logger?.debug(USER_IN_ROLLOUT, userId, feature.key);
728824
decideReasons.push([USER_IN_ROLLOUT, userId, feature.key]);
729825
} else {
730826
this.logger?.debug(USER_NOT_IN_ROLLOUT, userId, feature.key);
731827
decideReasons.push([USER_NOT_IN_ROLLOUT, userId, feature.key]);
732828
}
733-
734-
decisions.push({
735-
result: rolloutDecision,
829+
830+
return opValue(op, {
831+
result: rolloutDecisionResult,
736832
reasons: decideReasons,
737833
});
738-
}
739-
740-
if(!shouldIgnoreUPS) {
741-
this.saveUserProfile(userId, userProfileTracker);
742-
}
743-
744-
return decisions;
745-
834+
});
746835
}
747836

748837
/**
@@ -766,16 +855,15 @@ export class DecisionService {
766855
user: OptimizelyUserContext,
767856
options: { [key: string]: boolean } = {}
768857
): DecisionResponse<DecisionObj> {
769-
return this.getVariationsForFeatureList(configObj, [feature], user, options)[0]
858+
return this.getVariationsForFeatureList('sync', configObj, [feature], user, options)[0]
770859
}
771860

772861
private getVariationForFeatureExperiment<O extends OpType>(
862+
op: O,
773863
configObj: ProjectConfig,
774864
feature: FeatureFlag,
775865
user: OptimizelyUserContext,
776-
shouldIgnoreUPS: boolean,
777-
userProfileTracker: UserProfileTracker,
778-
op: O,
866+
userProfileTracker?: UserProfileTracker,
779867
): OpValue<O, DecisionResult> {
780868

781869
// const decideReasons: (string | number)[][] = [];
@@ -810,7 +898,7 @@ export class DecisionService {
810898
}
811899

812900
const decisionVariation = this.getVariationFromExperimentRule(
813-
configObj, feature.key, experiment, user, shouldIgnoreUPS, userProfileTracker, op
901+
op, configObj, feature.key, experiment, user, userProfileTracker,
814902
);
815903

816904
return opThen(op, decisionVariation, (decisionVariation) => {
@@ -1309,13 +1397,12 @@ export class DecisionService {
13091397
}
13101398

13111399
private getVariationFromExperimentRule<O extends OpType>(
1400+
op: O,
13121401
configObj: ProjectConfig,
13131402
flagKey: string,
13141403
rule: Experiment,
13151404
user: OptimizelyUserContext,
1316-
shouldIgnoreUPS: boolean,
1317-
userProfileTracker: UserProfileTracker,
1318-
op: O,
1405+
userProfileTracker?: UserProfileTracker,
13191406
): OpValue<O, VariationResult> {
13201407
const decideReasons: (string | number)[][] = [];
13211408

@@ -1330,7 +1417,7 @@ export class DecisionService {
13301417
reasons: decideReasons,
13311418
});
13321419
}
1333-
const decisionVariation = this.resolveVariation(configObj, rule, user, shouldIgnoreUPS, userProfileTracker, op);
1420+
const decisionVariation = this.resolveVariation(op, configObj, rule, user, userProfileTracker);
13341421

13351422
const response = opThen(op, decisionVariation, (variationResult) => {
13361423
decideReasons.push(...variationResult.reasons);

lib/message/error_message.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,6 @@ export const UNABLE_TO_ATTACH_UNLOAD = 'unable to bind optimizely.close() to pag
108108
export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item';
109109
export const CMAB_FETCH_FAILED = 'CMAB decision fetch failed with status: %s';
110110
export const INVALID_CMAB_FETCH_RESPONSE = 'Invalid CMAB fetch response';
111+
export const PROMISE_NOT_ALLOWED = "Promise value is not allowed in sync operation";
111112

112113
export const messages: string[] = [];

0 commit comments

Comments
 (0)