Skip to content
Merged
24 changes: 21 additions & 3 deletions lib/client_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,18 @@ import { extractConfigManager } from "./project_config/config_manager_factory";
import { extractEventProcessor } from "./event_processor/event_processor_factory";
import { extractOdpManager } from "./odp/odp_manager_factory";
import { extractVuidManager } from "./vuid/vuid_manager_factory";

import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from "./utils/enums";
import { RequestHandler } from "./utils/http_request_handler/http";
import { CLIENT_VERSION, DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT, JAVASCRIPT_CLIENT_ENGINE } from "./utils/enums";
import Optimizely from "./optimizely";
import { DefaultCmabClient } from "./core/decision_service/cmab/cmab_client";
import { CmabCacheValue, DefaultCmabService } from "./core/decision_service/cmab/cmab_service";
import { InMemoryLruCache } from "./utils/cache/in_memory_lru_cache";

export type OptimizelyFactoryConfig = Config & {
requestHandler: RequestHandler;
}

export const getOptimizelyInstance = (config: Config): Client | null => {
export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Client | null => {
let logger: Maybe<LoggerFacade>;

try {
Expand All @@ -43,6 +50,7 @@ export const getOptimizelyInstance = (config: Config): Client | null => {
userProfileService,
defaultDecideOptions,
disposable,
requestHandler,
} = config;

const errorNotifier = config.errorNotifier ? extractErrorNotifier(config.errorNotifier) : undefined;
Expand All @@ -52,7 +60,17 @@ export const getOptimizelyInstance = (config: Config): Client | null => {
const odpManager = config.odpManager ? extractOdpManager(config.odpManager) : undefined;
const vuidManager = config.vuidManager ? extractVuidManager(config.vuidManager) : undefined;

const cmabClient = new DefaultCmabClient({
requestHandler,
});

const cmabService = new DefaultCmabService({
cmabClient,
cmabCache: new InMemoryLruCache<CmabCacheValue>(DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT),
});

const optimizelyOptions = {
cmabService,
clientEngine: clientEngine || JAVASCRIPT_CLIENT_ENGINE,
clientVersion: clientVersion || CLIENT_VERSION,
jsonSchemaValidator,
Expand Down
3 changes: 2 additions & 1 deletion lib/core/bucketer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { INVALID_GROUP_ID } from 'error_message';
import { OptimizelyError } from '../../error/optimizly_error';
import { generateBucketValue } from './bucket_value_generator';
import { DecisionReason } from '../decision_service';

export const USER_NOT_IN_ANY_EXPERIMENT = 'User %s is not in any experiment of group %s.';
export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is not in experiment %s of group %s.';
Expand All @@ -52,7 +53,7 @@ const RANDOM_POLICY = 'random';
* null if user is not bucketed into any experiment and the decide reasons.
*/
export const bucket = function(bucketerParams: BucketerParams): DecisionResponse<string | null> {
const decideReasons: (string | number)[][] = [];
const decideReasons: DecisionReason[] = [];
// Check if user is in a random group; if so, check if user is bucketed into a specific experiment
const experiment = bucketerParams.experimentIdMap[bucketerParams.experimentId];
const groupId = experiment['groupId'];
Expand Down
63 changes: 32 additions & 31 deletions lib/core/decision_service/cmab/cmab_service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe('DefaultCmabService', () => {
});

const ruleId = '1234';
const variation = await cmabService.getDecision(projectConfig, userContext, ruleId, []);
const variation = await cmabService.getDecision(projectConfig, userContext, ruleId, {});

expect(variation.variationId).toEqual('123');
expect(uuidValidate(variation.cmabUuid)).toBe(true);
Expand Down Expand Up @@ -101,8 +101,8 @@ describe('DefaultCmabService', () => {
gender: 'male'
});

await cmabService.getDecision(projectConfig, userContext, '1234', []);
await cmabService.getDecision(projectConfig, userContext, '5678', []);
await cmabService.getDecision(projectConfig, userContext, '1234', {});
await cmabService.getDecision(projectConfig, userContext, '5678', {});

expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2);
expect(mockCmabClient.fetchDecision.mock.calls[0][2]).toEqual({
Expand Down Expand Up @@ -136,7 +136,7 @@ describe('DefaultCmabService', () => {
gender: 'male'
});

const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', []);
const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', {});

const userContext12 = mockUserContext('user123', {
country: 'US',
Expand All @@ -145,7 +145,7 @@ describe('DefaultCmabService', () => {
gender: 'female'
});

const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', []);
const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', {});
expect(variation11.variationId).toEqual('123');
expect(variation12.variationId).toEqual('123');
expect(variation11.cmabUuid).toEqual(variation12.cmabUuid);
Expand All @@ -157,14 +157,14 @@ describe('DefaultCmabService', () => {
age: '30',
});

const variation21 = await cmabService.getDecision(projectConfig, userContext21, '5678', []);
const variation21 = await cmabService.getDecision(projectConfig, userContext21, '5678', {});

const userContext22 = mockUserContext('user456', {
country: 'BD',
age: '35',
});

const variation22 = await cmabService.getDecision(projectConfig, userContext22, '5678', []);
const variation22 = await cmabService.getDecision(projectConfig, userContext22, '5678', {});
expect(variation21.variationId).toEqual('456');
expect(variation22.variationId).toEqual('456');
expect(variation21.cmabUuid).toEqual(variation22.cmabUuid);
Expand Down Expand Up @@ -192,7 +192,7 @@ describe('DefaultCmabService', () => {
gender: 'male'
});

const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', []);
const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', {});

const userContext12 = mockUserContext('user123', {
gender: 'female',
Expand All @@ -201,7 +201,7 @@ describe('DefaultCmabService', () => {
age: '25',
});

const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', []);
const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', {});
expect(variation11.variationId).toEqual('123');
expect(variation12.variationId).toEqual('123');
expect(variation11.cmabUuid).toEqual(variation12.cmabUuid);
Expand All @@ -227,9 +227,9 @@ describe('DefaultCmabService', () => {
age: '25',
});

const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', []);
const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', {});

const variation2 = await cmabService.getDecision(projectConfig, userContext, '5678', []);
const variation2 = await cmabService.getDecision(projectConfig, userContext, '5678', {});

expect(variation1.variationId).toEqual('123');
expect(variation2.variationId).toEqual('456');
Expand Down Expand Up @@ -260,9 +260,9 @@ describe('DefaultCmabService', () => {
age: '25',
});

const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', []);
const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', {});

const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', []);
const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', {});
expect(variation1.variationId).toEqual('123');
expect(variation2.variationId).toEqual('456');
expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid);
Expand All @@ -289,7 +289,7 @@ describe('DefaultCmabService', () => {
gender: 'male'
});

const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', []);
const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', {});

const userContext2 = mockUserContext('user123', {
country: 'US',
Expand All @@ -298,7 +298,7 @@ describe('DefaultCmabService', () => {
gender: 'male'
});

const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', []);
const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', {});
expect(variation1.variationId).toEqual('123');
expect(variation2.variationId).toEqual('456');
expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid);
Expand All @@ -325,13 +325,13 @@ describe('DefaultCmabService', () => {
gender: 'male'
});

const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', []);
const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', {});

const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', [
OptimizelyDecideOption.IGNORE_CMAB_CACHE,
]);
const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', {
[OptimizelyDecideOption.IGNORE_CMAB_CACHE]: true,
});

const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', []);
const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', {});

expect(variation1.variationId).toEqual('123');
expect(variation2.variationId).toEqual('456');
Expand Down Expand Up @@ -367,18 +367,19 @@ describe('DefaultCmabService', () => {
age: '50'
});

const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', []);
const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', {});
expect(variation1.variationId).toEqual('123');

const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', []);
const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', {});
expect(variation2.variationId).toEqual('456');

const variation3 = await cmabService.getDecision(projectConfig, userContext1, '1234', [
OptimizelyDecideOption.RESET_CMAB_CACHE,
]);
const variation3 = await cmabService.getDecision(projectConfig, userContext1, '1234', {
[OptimizelyDecideOption.RESET_CMAB_CACHE]: true,
});

expect(variation3.variationId).toEqual('789');

const variation4 = await cmabService.getDecision(projectConfig, userContext2, '1234', []);
const variation4 = await cmabService.getDecision(projectConfig, userContext2, '1234', {});
expect(variation4.variationId).toEqual('101112');
});

Expand All @@ -401,13 +402,13 @@ describe('DefaultCmabService', () => {
gender: 'male'
});

const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', []);
const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', {});

const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', [
OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE,
]);
const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', {
[OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]: true,
});

const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', []);
const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', {});

expect(variation1.variationId).toEqual('123');
expect(variation2.variationId).toEqual('456');
Expand Down
22 changes: 11 additions & 11 deletions lib/core/decision_service/cmab/cmab_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
*/

import { LoggerFacade } from "../../../logging/logger";
import OptimizelyUserContext from "../../../optimizely_user_context"
import { IOptimizelyUserContext } from "../../../optimizely_user_context";
import { ProjectConfig } from "../../../project_config/project_config"
import { OptimizelyDecideOption, UserAttributes } from "../../../shared_types"
import { Cache } from "../../../utils/cache/cache";
import { CmabClient } from "./cmab_client";
import { v4 as uuidV4 } from 'uuid';
import murmurhash from "murmurhash";
import { a } from "vitest/dist/chunks/suite.CcK46U-P";
import { DecideOptionsMap } from "..";

export type CmabDecision = {
variationId: string,
Expand All @@ -32,16 +32,16 @@ export type CmabDecision = {
export interface CmabService {
/**
* Get variation id for the user
* @param {OptimizelyUserContext} userContext
* @param {IOptimizelyUserContext} userContext
* @param {string} ruleId
* @param {OptimizelyDecideOption[]} options
* @return {Promise<CmabDecision>}
*/
getDecision(
projectConfig: ProjectConfig,
userContext: OptimizelyUserContext,
userContext: IOptimizelyUserContext,
ruleId: string,
options: OptimizelyDecideOption[]
options: DecideOptionsMap,
): Promise<CmabDecision>
}

Expand Down Expand Up @@ -70,23 +70,23 @@ export class DefaultCmabService implements CmabService {

async getDecision(
projectConfig: ProjectConfig,
userContext: OptimizelyUserContext,
userContext: IOptimizelyUserContext,
ruleId: string,
options: OptimizelyDecideOption[]
options: DecideOptionsMap,
): Promise<CmabDecision> {
const filteredAttributes = this.filterAttributes(projectConfig, userContext, ruleId);

if (options.includes(OptimizelyDecideOption.IGNORE_CMAB_CACHE)) {
if (options[OptimizelyDecideOption.IGNORE_CMAB_CACHE]) {
return this.fetchDecision(ruleId, userContext.getUserId(), filteredAttributes);
}

if (options.includes(OptimizelyDecideOption.RESET_CMAB_CACHE)) {
if (options[OptimizelyDecideOption.RESET_CMAB_CACHE]) {
this.cmabCache.clear();
}

const cacheKey = this.getCacheKey(userContext.getUserId(), ruleId);

if (options.includes(OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE)) {
if (options[OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]) {
this.cmabCache.remove(cacheKey);
}

Expand Down Expand Up @@ -125,7 +125,7 @@ export class DefaultCmabService implements CmabService {

private filterAttributes(
projectConfig: ProjectConfig,
userContext: OptimizelyUserContext,
userContext: IOptimizelyUserContext,
ruleId: string
): UserAttributes {
const filteredAttributes: UserAttributes = {};
Expand Down
Loading
Loading