Skip to content

Commit f8c62e4

Browse files
committed
up
1 parent 18bf3f3 commit f8c62e4

File tree

10 files changed

+132
-89
lines changed

10 files changed

+132
-89
lines changed

lib/client_factory.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,19 @@ import { extractConfigManager } from "./project_config/config_manager_factory";
2424
import { extractEventProcessor } from "./event_processor/event_processor_factory";
2525
import { extractOdpManager } from "./odp/odp_manager_factory";
2626
import { extractVuidManager } from "./vuid/vuid_manager_factory";
27-
27+
import{ RequestHandler } from "./utils/http_request_handler/http";
2828
import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from "./utils/enums";
2929
import Optimizely from "./optimizely";
30+
import { DefaultCmabClient } from "./core/decision_service/cmab/cmab_client";
31+
import { NodeRequestHandler } from "./utils/http_request_handler/request_handler.node";
32+
import { CmabCacheValue, DefaultCmabService } from "./core/decision_service/cmab/cmab_service";
33+
import { InMemoryLruCache } from "./utils/cache/in_memory_lru_cache";
34+
35+
export type OptimizelyFactoryConfig = Config & {
36+
requestHandler: RequestHandler;
37+
}
3038

31-
export const getOptimizelyInstance = (config: Config): Client | null => {
39+
export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Client | null => {
3240
let logger: Maybe<LoggerFacade>;
3341

3442
try {
@@ -43,6 +51,7 @@ export const getOptimizelyInstance = (config: Config): Client | null => {
4351
userProfileService,
4452
defaultDecideOptions,
4553
disposable,
54+
requestHandler,
4655
} = config;
4756

4857
const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined;
@@ -52,7 +61,17 @@ export const getOptimizelyInstance = (config: Config): Client | null => {
5261
const odpManager = config.odpManager ? extractOdpManager(config.odpManager) : undefined;
5362
const vuidManager = config.vuidManager ? extractVuidManager(config.vuidManager) : undefined;
5463

64+
const cmabClient = new DefaultCmabClient({
65+
requestHandler,
66+
});
67+
68+
const cmabService = new DefaultCmabService({
69+
cmabClient,
70+
cmabCache: new InMemoryLruCache<CmabCacheValue>(10000),
71+
});
72+
5573
const optimizelyOptions = {
74+
cmabService,
5675
clientEngine: clientEngine || JAVASCRIPT_CLIENT_ENGINE,
5776
clientVersion: clientVersion || CLIENT_VERSION,
5877
jsonSchemaValidator,

lib/core/bucketer/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
import { INVALID_GROUP_ID } from 'error_message';
2828
import { OptimizelyError } from '../../error/optimizly_error';
2929
import { generateBucketValue } from './bucket_value_generator';
30+
import { DecisionReason } from '../decision_service';
3031

3132
export const USER_NOT_IN_ANY_EXPERIMENT = 'User %s is not in any experiment of group %s.';
3233
export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is not in experiment %s of group %s.';
@@ -52,7 +53,7 @@ const RANDOM_POLICY = 'random';
5253
* null if user is not bucketed into any experiment and the decide reasons.
5354
*/
5455
export const bucket = function(bucketerParams: BucketerParams): DecisionResponse<string | null> {
55-
const decideReasons: (string | number)[][] = [];
56+
const decideReasons: DecisionReason[] = [];
5657
// Check if user is in a random group; if so, check if user is bucketed into a specific experiment
5758
const experiment = bucketerParams.experimentIdMap[bucketerParams.experimentId];
5859
const groupId = experiment['groupId'];

lib/core/decision_service/index.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,11 +1248,11 @@ describe('DecisionService', () => {
12481248

12491249
expect(resolveVariationSpy).toHaveBeenCalledTimes(3);
12501250
expect(resolveVariationSpy).toHaveBeenNthCalledWith(1,
1251-
config, config.experimentKeyMap['exp_1'], user, false, expect.anything());
1251+
config, config.experimentKeyMap['exp_1'], user, expect.anything());
12521252
expect(resolveVariationSpy).toHaveBeenNthCalledWith(2,
1253-
config, config.experimentKeyMap['exp_2'], user, false, expect.anything());
1253+
config, config.experimentKeyMap['exp_2'], user, expect.anything());
12541254
expect(resolveVariationSpy).toHaveBeenNthCalledWith(3,
1255-
config, config.experimentKeyMap['exp_3'], user, false, expect.anything());
1255+
config, config.experimentKeyMap['exp_3'], user, expect.anything());
12561256

12571257
expect(mockBucket).toHaveBeenCalledTimes(0);
12581258
});

lib/core/decision_service/index.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,9 @@ interface UserProfileTracker {
131131
isProfileUpdated: boolean;
132132
}
133133

134-
type DecisionReason = [string, ...any[]];
135-
type VariationResult = DecisionResponse<string | null>;
136-
137-
type DecisionResult = DecisionResponse<DecisionObj>;
134+
export type DecisionReason = [string, ...any[]];
135+
export type VariationResult = DecisionResponse<string | null>;
136+
export type DecisionResult = DecisionResponse<DecisionObj>;
138137

139138
/**
140139
* Optimizely's decision service that determines which variation of an experiment the user will be allocated to.
@@ -427,10 +426,8 @@ export class DecisionService {
427426
private resolveExperimentBucketMap<O extends OpType>(
428427
op: O,
429428
userId: string,
430-
attributes?: UserAttributes,
429+
attributes: UserAttributes = {},
431430
): OpValue<O, ExperimentBucketMap> {
432-
attributes = attributes || {};
433-
434431
return opThen(op, this.getUserProfile(userId, op), (userProfile) => {
435432
const experimentBucketMap = {
436433
...userProfile?.experiment_bucket_map,
@@ -691,7 +688,15 @@ export class DecisionService {
691688
* @returns {DecisionResponse<DecisionObj>[]} - An array of DecisionResponse containing objects with
692689
* experiment, variation, decisionSource properties, and decision reasons.
693690
*/
694-
getVariationsForFeatureList<OP extends OpType>(
691+
getVariationsForFeatureList(
692+
configObj: ProjectConfig,
693+
featureFlags: FeatureFlag[],
694+
user: OptimizelyUserContext,
695+
options: { [key: string]: boolean } = {}): DecisionResult[] {
696+
return this.resolveVariationsForFeatureList('sync', configObj, featureFlags, user, options);
697+
}
698+
699+
resolveVariationsForFeatureList<OP extends OpType>(
695700
op: OP,
696701
configObj: ProjectConfig,
697702
featureFlags: FeatureFlag[],
@@ -853,7 +858,7 @@ export class DecisionService {
853858
user: OptimizelyUserContext,
854859
options: { [key: string]: boolean } = {}
855860
): DecisionResponse<DecisionObj> {
856-
return this.getVariationsForFeatureList('sync', configObj, [feature], user, options)[0]
861+
return this.resolveVariationsForFeatureList('sync', configObj, [feature], user, options)[0]
857862
}
858863

859864
private getVariationForFeatureExperiment<O extends OpType>(

lib/index.browser.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_b
1818
import { getOptimizelyInstance } from './client_factory';
1919
import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher';
2020
import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums';
21+
import { BrowserRequestHandler } from './utils/http_request_handler/request_handler.browser';
2122

2223
/**
2324
* Creates an instance of the Optimizely class
@@ -26,7 +27,10 @@ import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums';
2627
* null on error
2728
*/
2829
export const createInstance = function(config: Config): Client | null {
29-
const client = getOptimizelyInstance(config);
30+
const client = getOptimizelyInstance({
31+
...config,
32+
requestHandler: new BrowserRequestHandler(),
33+
});
3034

3135
if (client) {
3236
const unloadEvent = 'onpagehide' in window ? 'pagehide' : 'unload';

lib/index.node.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { NODE_CLIENT_ENGINE } from './utils/enums';
1717
import { Client, Config } from './shared_types';
1818
import { getOptimizelyInstance } from './client_factory';
1919
import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher';
20+
import { NodeRequestHandler } from './utils/http_request_handler/request_handler.node';
2021

2122
/**
2223
* Creates an instance of the Optimizely class
@@ -28,6 +29,7 @@ export const createInstance = function(config: Config): Client | null {
2829
const nodeConfig = {
2930
...config,
3031
clientEnging: config.clientEngine || NODE_CLIENT_ENGINE,
32+
requestHandler: new NodeRequestHandler(),
3133
}
3234

3335
return getOptimizelyInstance(nodeConfig);

lib/index.react_native.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Client, Config } from './shared_types';
2020
import { getOptimizelyInstance } from './client_factory';
2121
import { REACT_NATIVE_JS_CLIENT_ENGINE } from './utils/enums';
2222
import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher';
23+
import { BrowserRequestHandler } from './utils/http_request_handler/request_handler.browser';
2324

2425
/**
2526
* Creates an instance of the Optimizely class
@@ -31,6 +32,7 @@ export const createInstance = function(config: Config): Client | null {
3132
const rnConfig = {
3233
...config,
3334
clientEngine: config.clientEngine || REACT_NATIVE_JS_CLIENT_ENGINE,
35+
requestHandler: new BrowserRequestHandler(),
3436
}
3537

3638
return getOptimizelyInstance(rnConfig);

lib/index.universal.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,19 @@ import { Client, Config } from './shared_types';
1717
import { getOptimizelyInstance } from './client_factory';
1818
import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums';
1919

20+
import { RequestHandler } from './utils/http_request_handler/http';
21+
22+
export type UniversalConfig = Config & {
23+
requestHandler: RequestHandler;
24+
}
25+
2026
/**
2127
* Creates an instance of the Optimizely class
2228
* @param {Config} config
2329
* @return {Client|null} the Optimizely client object
2430
* null on error
2531
*/
26-
export const createInstance = function(config: Config): Client | null {
32+
export const createInstance = function(config: UniversalConfig): Client | null {
2733
return getOptimizelyInstance(config);
2834
};
2935

lib/optimizely/index.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ describe('Optimizely', () => {
5252
eventProcessor,
5353
odpManager,
5454
disposable: true,
55+
cmabService: {} as any
5556
});
5657

5758
expect(projectConfigManager.makeDisposable).toHaveBeenCalled();

lib/optimizely/index.ts

Lines changed: 75 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ import { ErrorNotifier } from '../error/error_notifier';
103103
import { ErrorReporter } from '../error/error_reporter';
104104
import { OptimizelyError } from '../error/optimizly_error';
105105
import { opThen, opValue } from '../utils/promise/opValue';
106+
import { CmabService } from '../core/decision_service/cmab/cmab_service';
106107

107108
const DEFAULT_ONREADY_TIMEOUT = 30000;
108109

@@ -119,6 +120,7 @@ type DecisionReasons = (string | number)[];
119120
export type OptimizelyOptions = {
120121
projectConfigManager: ProjectConfigManager;
121122
UNSTABLE_conditionEvaluators?: unknown;
123+
cmabService: CmabService;
122124
clientEngine: string;
123125
clientVersion?: string;
124126
errorNotifier?: ErrorNotifier;
@@ -226,6 +228,7 @@ export default class Optimizely extends BaseService implements Client {
226228

227229
this.decisionService = createDecisionService({
228230
userProfileService: userProfileService,
231+
cmabService: config.cmabService,
229232
logger: this.logger,
230233
UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators,
231234
});
@@ -1532,77 +1535,77 @@ export default class Optimizely extends BaseService implements Client {
15321535
* @param {OptimizelyDecideOption[]} options An array of options for decision-making.
15331536
* @return {[key: string]: OptimizelyDecision} An object of decision results mapped by flag keys.
15341537
*/
1535-
decideForKeys_(
1536-
user: OptimizelyUserContext,
1537-
keys: string[],
1538-
options: OptimizelyDecideOption[] = [],
1539-
ignoreEnabledFlagOption?:boolean
1540-
): Record<string, OptimizelyDecision> {
1541-
const decisionMap: Record<string, OptimizelyDecision> = {};
1542-
const flagDecisions: Record<string, DecisionObj> = {};
1543-
const decisionReasonsMap: Record<string, DecisionReasons[]> = {};
1544-
const flagsWithoutForcedDecision = [];
1545-
const validKeys = [];
1546-
1547-
const configObj = this.getProjectConfig()
1548-
1549-
if (!configObj) {
1550-
this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decideForKeys');
1551-
return decisionMap;
1552-
}
1553-
if (keys.length === 0) {
1554-
return decisionMap;
1555-
}
1556-
1557-
const allDecideOptions = this.getAllDecideOptions(options);
1558-
1559-
if (ignoreEnabledFlagOption) {
1560-
delete allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY];
1561-
}
1562-
1563-
for(const key of keys) {
1564-
const feature = configObj.featureKeyMap[key];
1565-
if (!feature) {
1566-
this.logger?.error(FEATURE_NOT_IN_DATAFILE, key);
1567-
decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]);
1568-
continue;
1569-
}
1570-
1571-
validKeys.push(key);
1572-
const forcedDecisionResponse = this.decisionService.findValidatedForcedDecision(configObj, user, key);
1573-
decisionReasonsMap[key] = forcedDecisionResponse.reasons
1574-
const variation = forcedDecisionResponse.result;
1575-
1576-
if (variation) {
1577-
flagDecisions[key] = {
1578-
experiment: null,
1579-
variation: variation,
1580-
decisionSource: DECISION_SOURCES.FEATURE_TEST,
1581-
};
1582-
} else {
1583-
flagsWithoutForcedDecision.push(feature)
1584-
}
1585-
}
1586-
1587-
const decisionList = this.decisionService.getVariationsForFeatureList(configObj, flagsWithoutForcedDecision, user, allDecideOptions);
1588-
1589-
for(let i = 0; i < flagsWithoutForcedDecision.length; i++) {
1590-
const key = flagsWithoutForcedDecision[i].key;
1591-
const decision = decisionList[i];
1592-
flagDecisions[key] = decision.result;
1593-
decisionReasonsMap[key] = [...decisionReasonsMap[key], ...decision.reasons];
1594-
}
1595-
1596-
for(const validKey of validKeys) {
1597-
const decision = this.generateDecision(user, validKey, flagDecisions[validKey], decisionReasonsMap[validKey], allDecideOptions, configObj);
1598-
1599-
if(!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || decision.enabled) {
1600-
decisionMap[validKey] = decision;
1601-
}
1602-
}
1603-
1604-
return decisionMap;
1605-
}
1538+
// decideForKeys_(
1539+
// user: OptimizelyUserContext,
1540+
// keys: string[],
1541+
// options: OptimizelyDecideOption[] = [],
1542+
// ignoreEnabledFlagOption?:boolean
1543+
// ): Record<string, OptimizelyDecision> {
1544+
// const decisionMap: Record<string, OptimizelyDecision> = {};
1545+
// const flagDecisions: Record<string, DecisionObj> = {};
1546+
// const decisionReasonsMap: Record<string, DecisionReasons[]> = {};
1547+
// const flagsWithoutForcedDecision = [];
1548+
// const validKeys = [];
1549+
1550+
// const configObj = this.getProjectConfig()
1551+
1552+
// if (!configObj) {
1553+
// this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decideForKeys');
1554+
// return decisionMap;
1555+
// }
1556+
// if (keys.length === 0) {
1557+
// return decisionMap;
1558+
// }
1559+
1560+
// const allDecideOptions = this.getAllDecideOptions(options);
1561+
1562+
// if (ignoreEnabledFlagOption) {
1563+
// delete allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY];
1564+
// }
1565+
1566+
// for(const key of keys) {
1567+
// const feature = configObj.featureKeyMap[key];
1568+
// if (!feature) {
1569+
// this.logger?.error(FEATURE_NOT_IN_DATAFILE, key);
1570+
// decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]);
1571+
// continue;
1572+
// }
1573+
1574+
// validKeys.push(key);
1575+
// const forcedDecisionResponse = this.decisionService.findValidatedForcedDecision(configObj, user, key);
1576+
// decisionReasonsMap[key] = forcedDecisionResponse.reasons
1577+
// const variation = forcedDecisionResponse.result;
1578+
1579+
// if (variation) {
1580+
// flagDecisions[key] = {
1581+
// experiment: null,
1582+
// variation: variation,
1583+
// decisionSource: DECISION_SOURCES.FEATURE_TEST,
1584+
// };
1585+
// } else {
1586+
// flagsWithoutForcedDecision.push(feature)
1587+
// }
1588+
// }
1589+
1590+
// const decisionList = this.decisionService.getVariationsForFeatureList(configObj, flagsWithoutForcedDecision, user, allDecideOptions);
1591+
1592+
// for(let i = 0; i < flagsWithoutForcedDecision.length; i++) {
1593+
// const key = flagsWithoutForcedDecision[i].key;
1594+
// const decision = decisionList[i];
1595+
// flagDecisions[key] = decision.result;
1596+
// decisionReasonsMap[key] = [...decisionReasonsMap[key], ...decision.reasons];
1597+
// }
1598+
1599+
// for(const validKey of validKeys) {
1600+
// const decision = this.generateDecision(user, validKey, flagDecisions[validKey], decisionReasonsMap[validKey], allDecideOptions, configObj);
1601+
1602+
// if(!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || decision.enabled) {
1603+
// decisionMap[validKey] = decision;
1604+
// }
1605+
// }
1606+
1607+
// return decisionMap;
1608+
// }
16061609

16071610
decideForKeys(
16081611
user: OptimizelyUserContext,
@@ -1679,7 +1682,7 @@ export default class Optimizely extends BaseService implements Client {
16791682

16801683
return opThen(
16811684
op,
1682-
this.decisionService.getVariationsForFeatureList(op, configObj, validFlags, user, allDecideOptions),
1685+
this.decisionService.resolveVariationsForFeatureList(op, configObj, validFlags, user, allDecideOptions),
16831686
(decisionList) => {
16841687
for(let i = 0; i < validFlags.length; i++) {
16851688
const key = validFlags[i].key;

0 commit comments

Comments
 (0)