From be5b02236669322194939650cec8afce731687c1 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 26 Mar 2024 01:57:08 +0600 Subject: [PATCH 01/24] refactor --- lib/core/odp/odp_config.ts | 248 ++++++++++++++--------- lib/core/odp/odp_manager.ts | 58 +++++- lib/plugins/odp_manager/index.browser.ts | 95 ++++++++- 3 files changed, 296 insertions(+), 105 deletions(-) diff --git a/lib/core/odp/odp_config.ts b/lib/core/odp/odp_config.ts index 8593dbd2d..a2b0affb5 100644 --- a/lib/core/odp/odp_config.ts +++ b/lib/core/odp/odp_config.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,106 +16,164 @@ import { checkArrayEquality } from '../../utils/fns'; -export class OdpConfig { - /** - * Host of ODP audience segments API. - * @private - */ - private _apiHost: string; - - /** - * Getter to retrieve the ODP server host - * @public - */ - get apiHost(): string { - return this._apiHost; - } +export type NoOdpIntegrationConfig = { + readonly integrated: false; +} - /** - * Public API key for the ODP account from which the audience segments will be fetched (optional). - * @private - */ - private _apiKey: string; - - /** - * Getter to retrieve the ODP API key - * @public - */ - get apiKey(): string { - return this._apiKey; - } +export type OdpIntegrationConfig = { + readonly integrated: true; + readonly apiHost: string; + readonly apiKey: string; + readonly pixelUrl?: string; + readonly segmentsToCheck?: string[]; +} - /** - * Url for sending events via pixel. - * @private - */ - private _pixelUrl: string; - - /** - * Getter to retrieve the ODP pixel URL - * @public - */ - get pixelUrl(): string { - return this._pixelUrl; - } +export type OdpConfig = (NoOdpIntegrationConfig | OdpIntegrationConfig) & { + equals(odpConfig: OdpConfig): boolean; +} - /** - * All ODP segments used in the current datafile (associated with apiHost/apiKey). - * @private - */ - private _segmentsToCheck: string[]; - - /** - * Getter for ODP segments to check - * @public - */ - get segmentsToCheck(): string[] { - return this._segmentsToCheck; +function areOdpConfigsEqual(config1: OdpConfig, config2: OdpConfig): boolean { + if (config1.integrated !== config2.integrated) { + return false; } - - constructor(apiKey?: string, apiHost?: string, pixelUrl?: string, segmentsToCheck?: string[]) { - this._apiKey = apiKey ?? ''; - this._apiHost = apiHost ?? ''; - this._pixelUrl = pixelUrl ?? ''; - this._segmentsToCheck = segmentsToCheck ?? []; + if (config1.integrated && config2.integrated) { + return ( + config1.apiHost === config2.apiHost && + config1.apiKey === config2.apiKey && + config1.pixelUrl === config2.pixelUrl && + checkArrayEquality(config1.segmentsToCheck || [], config2.segmentsToCheck || []) + ); } + return true; +} - /** - * Update the ODP configuration details - * @param {OdpConfig} config New ODP Config to potentially update self with - * @returns true if configuration was updated successfully - */ - update(config: OdpConfig): boolean { - if (this.equals(config)) { - return false; - } else { - if (config.apiKey) this._apiKey = config.apiKey; - if (config.apiHost) this._apiHost = config.apiHost; - if (config.pixelUrl) this._pixelUrl = config.pixelUrl; - if (config.segmentsToCheck) this._segmentsToCheck = config.segmentsToCheck; - - return true; +export function createOdpIntegrationConfig( + apiHost: string, + apiKey: string, + pixelUrl?: string, + segmentsToCheck?: string[] +): OdpConfig { + return { + integrated: true, + apiHost, + apiKey, + pixelUrl, + segmentsToCheck, + equals: function(odpConfig: OdpConfig) { + return areOdpConfigsEqual(this, odpConfig) } - } - - /** - * Determines if ODP configuration has the minimum amount of information - */ - isReady(): boolean { - return !!this._apiKey && !!this._apiHost; - } + }; +} - /** - * Detects if there are any changes between the current and incoming ODP Configs - * @param configToCompare ODP Configuration to check self against for equality - * @returns Boolean based on if the current ODP Config is equivalent to the incoming ODP Config - */ - equals(configToCompare: OdpConfig): boolean { - return ( - this._apiHost === configToCompare._apiHost && - this._apiKey === configToCompare._apiKey && - this._pixelUrl === configToCompare._pixelUrl && - checkArrayEquality(this.segmentsToCheck, configToCompare._segmentsToCheck) - ); - } +export function createNoOdpIntegrationConfig(): OdpConfig { + return { + integrated: false, + equals: function (odpConfig: OdpConfig) { + return areOdpConfigsEqual(this, odpConfig) + } + }; } + +// export class OdpConfig { +// /** +// * Host of ODP audience segments API. +// * @private +// */ +// private _apiHost: string; + +// /** +// * Getter to retrieve the ODP server host +// * @public +// */ +// get apiHost(): string { +// return this._apiHost; +// } + +// /** +// * Public API key for the ODP account from which the audience segments will be fetched (optional). +// * @private +// */ +// private _apiKey: string; + +// /** +// * Getter to retrieve the ODP API key +// * @public +// */ +// get apiKey(): string { +// return this._apiKey; +// } + +// /** +// * Url for sending events via pixel. +// * @private +// */ +// private _pixelUrl: string; + +// /** +// * Getter to retrieve the ODP pixel URL +// * @public +// */ +// get pixelUrl(): string { +// return this._pixelUrl; +// } + +// /** +// * All ODP segments used in the current datafile (associated with apiHost/apiKey). +// * @private +// */ +// private _segmentsToCheck: string[]; + +// /** +// * Getter for ODP segments to check +// * @public +// */ +// get segmentsToCheck(): string[] { +// return this._segmentsToCheck; +// } + +// constructor(apiKey?: string, apiHost?: string, pixelUrl?: string, segmentsToCheck?: string[]) { +// this._apiKey = apiKey ?? ''; +// this._apiHost = apiHost ?? ''; +// this._pixelUrl = pixelUrl ?? ''; +// this._segmentsToCheck = segmentsToCheck ?? []; +// } + +// /** +// * Update the ODP configuration details +// * @param {OdpConfig} config New ODP Config to potentially update self with +// * @returns true if configuration was updated successfully +// */ +// update(config: OdpConfig): boolean { +// if (this.equals(config)) { +// return false; +// } else { +// if (config.apiKey) this._apiKey = config.apiKey; +// if (config.apiHost) this._apiHost = config.apiHost; +// if (config.pixelUrl) this._pixelUrl = config.pixelUrl; +// if (config.segmentsToCheck) this._segmentsToCheck = config.segmentsToCheck; + +// return true; +// } +// } + +// /** +// * Determines if ODP configuration has the minimum amount of information +// */ +// isReady(): boolean { +// return !!this._apiKey && !!this._apiHost; +// } + +// /** +// * Detects if there are any changes between the current and incoming ODP Configs +// * @param configToCompare ODP Configuration to check self against for equality +// * @returns Boolean based on if the current ODP Config is equivalent to the incoming ODP Config +// */ +// equals(configToCompare: OdpConfig): boolean { +// return ( +// this._apiHost === configToCompare._apiHost && +// this._apiKey === configToCompare._apiKey && +// this._pixelUrl === configToCompare._pixelUrl && +// checkArrayEquality(this.segmentsToCheck, configToCompare._segmentsToCheck) +// ); +// } +// } diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index ca3fc7f77..6ea9acc93 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -32,7 +32,8 @@ import { OdpEvent } from './odp_event'; * Optimizely Data Platform (ODP) / Advanced Audience Targeting (AAT) */ export interface IOdpManager { - initPromise?: Promise; + onInit(): Promise; + // initPromise?: Promise; enabled: boolean; @@ -40,7 +41,7 @@ export interface IOdpManager { eventManager: IOdpEventManager | undefined; - updateSettings({ apiKey, apiHost, pixelUrl, segmentsToCheck }: OdpConfig): boolean; + updateSettings(odpConfig: OdpConfig): boolean; close(): void; @@ -52,6 +53,8 @@ export interface IOdpManager { isVuidEnabled(): boolean; + initializeVuid(): Promise; + getVuid(): string | undefined; } @@ -67,19 +70,21 @@ export abstract class OdpManager implements IOdpManager { /** * Switch to enable/disable ODP Manager functionality */ - enabled = true; + enabled: boolean; + vuidManger: VuidManager; + /** * ODP Segment Manager which provides an interface to the remote ODP server (GraphQL API) for audience segments mapping. * It fetches all qualified segments for the given user context and manages the segments cache for all user contexts. */ - segmentManager: IOdpSegmentManager | undefined; + segmentManager: IOdpSegmentManager; /** * ODP Event Manager which provides an interface to the remote ODP server (REST API) for events. * It will queue all pending events (persistent) and send them (in batches of up to 10 events) to the ODP server when possible. */ - eventManager: IOdpEventManager | undefined; + eventManager: IOdpEventManager; /** * Handler for recording execution logs @@ -90,14 +95,51 @@ export abstract class OdpManager implements IOdpManager { /** * ODP configuration settings for identifying the target API and segments */ - odpConfig: OdpConfig = new OdpConfig(); // TODO: Consider making private and adding public accessors + // odpConfig: OdpConfig = new OdpConfig(); // TODO: Consider making private and adding public accessors + odpConfig?: OdpConfig; + + // TODO: Consider accepting logger as a parameter and initializing it in constructor instead + private constructor({ + odpConfig, + vuidManager, + segmentManger, + eventManager, + }: { + odpConfig?: OdpConfig; + vuidManager: VuidManager; + segmentManger: IOdpSegmentManager; + eventManager: IOdpEventManager; + }) { + this.enabled = !!odpConfig; + this.odpConfig = odpConfig; + this.vuidManger = vuidManager; + this.segmentManager = segmentManger; + this.eventManager = eventManager; + + } - constructor() {} // TODO: Consider accepting logger as a parameter and initializing it in constructor instead + + + // start(): Promise { + // return Promise.resolve(); + // } + + // stop(): Promise { + // return Promise.resolve(); + // } + + onInit(): Promise { + return this.initPromise; + } /** * Provides a method to update ODP Manager's ODP Config API Key, API Host, and Audience Segments */ - updateSettings({ apiKey, apiHost, pixelUrl, segmentsToCheck }: OdpConfig): boolean { + updateSettings(odpConfig: OdpConfig): boolean { + if (!odpConfig.integrated) { + this.close(); + } + if (!this.enabled) { return false; } diff --git a/lib/plugins/odp_manager/index.browser.ts b/lib/plugins/odp_manager/index.browser.ts index 171b93566..3f933d870 100644 --- a/lib/plugins/odp_manager/index.browser.ts +++ b/lib/plugins/odp_manager/index.browser.ts @@ -40,6 +40,7 @@ import { BrowserOdpEventApiManager } from '../odp/event_api_manager/index.browse import { BrowserOdpEventManager } from '../odp/event_manager/index.browser'; import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../../core/odp/odp_segment_api_manager'; +import { OdpConfig } from 'lib/core/odp/odp_config'; interface BrowserOdpManagerConfig { logger?: LogHandler; @@ -51,10 +52,100 @@ export class BrowserOdpManager extends OdpManager { static cache = new BrowserAsyncStorageCache(); vuid?: string; - constructor({ logger, odpOptions }: BrowserOdpManagerConfig) { + // constructor({ logger, odpOptions }: BrowserOdpManagerConfig) { + // super(); + + // this.logger = logger || getLogger(); + + // if (odpOptions?.disabled) { + // this.initPromise = Promise.resolve(); + // this.enabled = false; + // this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); + // return; + // } + + // const browserClientEngine = JAVASCRIPT_CLIENT_ENGINE; + // const browserClientVersion = CLIENT_VERSION; + + // let customSegmentRequestHandler; + + // if (odpOptions?.segmentsRequestHandler) { + // customSegmentRequestHandler = odpOptions.segmentsRequestHandler; + // } else { + // customSegmentRequestHandler = new BrowserRequestHandler( + // this.logger, + // odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS + // ); + // } + + // // Set up Segment Manager (Audience Segments GraphQL API Interface) + // if (odpOptions?.segmentManager) { + // this.segmentManager = odpOptions.segmentManager; + // this.segmentManager.updateSettings(this.odpConfig); + // } else { + // this.segmentManager = new OdpSegmentManager( + // this.odpConfig, + // odpOptions?.segmentsCache || + // new BrowserLRUCache({ + // maxSize: odpOptions?.segmentsCacheSize, + // timeout: odpOptions?.segmentsCacheTimeout, + // }), + // new OdpSegmentApiManager(customSegmentRequestHandler, this.logger) + // ); + // } + + // let customEventRequestHandler; + + // if (odpOptions?.eventRequestHandler) { + // customEventRequestHandler = odpOptions.eventRequestHandler; + // } else { + // customEventRequestHandler = new BrowserRequestHandler( + // this.logger, + // odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS + // ); + // } + + // // Set up Events Manager (Events REST API Interface) + // if (odpOptions?.eventManager) { + // this.eventManager = odpOptions.eventManager; + // this.eventManager.updateSettings(this.odpConfig); + // } else { + // this.eventManager = new BrowserOdpEventManager({ + // odpConfig: this.odpConfig, + // apiManager: new BrowserOdpEventApiManager(customEventRequestHandler, this.logger), + // logger: this.logger, + // clientEngine: browserClientEngine, + // clientVersion: browserClientVersion, + // flushInterval: odpOptions?.eventFlushInterval, + // batchSize: odpOptions?.eventBatchSize, + // queueSize: odpOptions?.eventQueueSize, + // userAgentParser: odpOptions?.userAgentParser, + // }); + // } + + // this.eventManager!.start(); + + // this.initPromise = this.initializeVuid(BrowserOdpManager.cache).catch(e => { + // this.logger.log(this.enabled ? LogLevel.ERROR : LogLevel.DEBUG, e); + // }); + // } + + private constructor({ + odpConfig, + vuidManager, + segmentManger, + eventManager, + }: { + odpConfig?: OdpConfig; + vuidManager: VuidManager; + segmentManger: OdpSegmentManager; + eventManager: BrowserOdpEventManager; + }) { super(); + } - this.logger = logger || getLogger(); + static createInstance({ logger, odpOptions }: BrowserOdpManagerConfig): BrowserOdpManager { + logger = logger || getLogger(); if (odpOptions?.disabled) { this.initPromise = Promise.resolve(); From 9705ec644bf631b7f78a536027c4c51298f7367f Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 29 Mar 2024 02:13:24 +0600 Subject: [PATCH 02/24] refactor: build working --- lib/core/odp/odp_config.ts | 195 ++++------------ lib/core/odp/odp_event_api_manager.ts | 11 +- lib/core/odp/odp_event_manager.ts | 101 +++++---- lib/core/odp/odp_manager.ts | 213 ++++++++++-------- lib/core/odp/odp_segment_manager.ts | 26 ++- lib/core/project_config/index.ts | 60 ++--- lib/index.browser.ts | 5 +- lib/index.node.ts | 5 +- lib/index.react_native.ts | 5 +- lib/optimizely/index.ts | 82 +++---- lib/optimizely_user_context/index.ts | 4 +- .../odp/event_api_manager/index.browser.ts | 24 +- .../odp/event_api_manager/index.node.ts | 13 +- lib/plugins/odp_manager/index.browser.ts | 189 ++++------------ lib/plugins/odp_manager/index.node.ts | 85 ++++--- lib/shared_types.ts | 2 + lib/utils/enums/index.ts | 4 +- lib/utils/promise/resolvablePromise.ts | 18 ++ 18 files changed, 454 insertions(+), 588 deletions(-) create mode 100644 lib/utils/promise/resolvablePromise.ts diff --git a/lib/core/odp/odp_config.ts b/lib/core/odp/odp_config.ts index a2b0affb5..e271bf71c 100644 --- a/lib/core/odp/odp_config.ts +++ b/lib/core/odp/odp_config.ts @@ -14,166 +14,71 @@ * limitations under the License. */ +import { config } from 'chai'; import { checkArrayEquality } from '../../utils/fns'; -export type NoOdpIntegrationConfig = { - readonly integrated: false; -} - -export type OdpIntegrationConfig = { - readonly integrated: true; +export class OdpConfig { + /** + * Host of ODP audience segments API. + */ readonly apiHost: string; - readonly apiKey: string; - readonly pixelUrl?: string; - readonly segmentsToCheck?: string[]; -} -export type OdpConfig = (NoOdpIntegrationConfig | OdpIntegrationConfig) & { - equals(odpConfig: OdpConfig): boolean; -} + /** + * Public API key for the ODP account from which the audience segments will be fetched (optional). + */ + readonly apiKey: string; -function areOdpConfigsEqual(config1: OdpConfig, config2: OdpConfig): boolean { - if (config1.integrated !== config2.integrated) { - return false; + /** + * Url for sending events via pixel. + */ + readonly pixelUrl: string; + + /** + * All ODP segments used in the current datafile (associated with apiHost/apiKey). + */ + readonly segmentsToCheck: string[]; + + constructor(apiKey: string, apiHost: string, pixelUrl: string, segmentsToCheck: string[]) { + this.apiKey = apiKey; + this.apiHost = apiHost; + this.pixelUrl = pixelUrl; + this.segmentsToCheck = segmentsToCheck; } - if (config1.integrated && config2.integrated) { + + /** + * Detects if there are any changes between the current and incoming ODP Configs + * @param configToCompare ODP Configuration to check self against for equality + * @returns Boolean based on if the current ODP Config is equivalent to the incoming ODP Config + */ + equals(configToCompare: OdpConfig): boolean { return ( - config1.apiHost === config2.apiHost && - config1.apiKey === config2.apiKey && - config1.pixelUrl === config2.pixelUrl && - checkArrayEquality(config1.segmentsToCheck || [], config2.segmentsToCheck || []) + this.apiHost === configToCompare.apiHost && + this.apiKey === configToCompare.apiKey && + this.pixelUrl === configToCompare.pixelUrl && + checkArrayEquality(this.segmentsToCheck, configToCompare.segmentsToCheck) ); } - return true; } -export function createOdpIntegrationConfig( - apiHost: string, - apiKey: string, - pixelUrl?: string, - segmentsToCheck?: string[] -): OdpConfig { - return { - integrated: true, - apiHost, - apiKey, - pixelUrl, - segmentsToCheck, - equals: function(odpConfig: OdpConfig) { - return areOdpConfigsEqual(this, odpConfig) - } - }; +type OdpNotIntegratedConfig = { + readonly integrated: false; } -export function createNoOdpIntegrationConfig(): OdpConfig { - return { - integrated: false, - equals: function (odpConfig: OdpConfig) { - return areOdpConfigsEqual(this, odpConfig) - } - }; +type OdpIntegratedConfig = { + readonly integrated: true; + readonly odpConfig: OdpConfig; } -// export class OdpConfig { -// /** -// * Host of ODP audience segments API. -// * @private -// */ -// private _apiHost: string; - -// /** -// * Getter to retrieve the ODP server host -// * @public -// */ -// get apiHost(): string { -// return this._apiHost; -// } - -// /** -// * Public API key for the ODP account from which the audience segments will be fetched (optional). -// * @private -// */ -// private _apiKey: string; - -// /** -// * Getter to retrieve the ODP API key -// * @public -// */ -// get apiKey(): string { -// return this._apiKey; -// } - -// /** -// * Url for sending events via pixel. -// * @private -// */ -// private _pixelUrl: string; - -// /** -// * Getter to retrieve the ODP pixel URL -// * @public -// */ -// get pixelUrl(): string { -// return this._pixelUrl; -// } - -// /** -// * All ODP segments used in the current datafile (associated with apiHost/apiKey). -// * @private -// */ -// private _segmentsToCheck: string[]; - -// /** -// * Getter for ODP segments to check -// * @public -// */ -// get segmentsToCheck(): string[] { -// return this._segmentsToCheck; -// } - -// constructor(apiKey?: string, apiHost?: string, pixelUrl?: string, segmentsToCheck?: string[]) { -// this._apiKey = apiKey ?? ''; -// this._apiHost = apiHost ?? ''; -// this._pixelUrl = pixelUrl ?? ''; -// this._segmentsToCheck = segmentsToCheck ?? []; -// } - -// /** -// * Update the ODP configuration details -// * @param {OdpConfig} config New ODP Config to potentially update self with -// * @returns true if configuration was updated successfully -// */ -// update(config: OdpConfig): boolean { -// if (this.equals(config)) { -// return false; -// } else { -// if (config.apiKey) this._apiKey = config.apiKey; -// if (config.apiHost) this._apiHost = config.apiHost; -// if (config.pixelUrl) this._pixelUrl = config.pixelUrl; -// if (config.segmentsToCheck) this._segmentsToCheck = config.segmentsToCheck; +export const odpIntegrationEquals = (config1: OdpIntegrationConfig, config2: OdpIntegrationConfig): boolean => { + if (config1.integrated !== config2.integrated) { + return false; + } -// return true; -// } -// } + if (config1.integrated && config2.integrated) { + return config1.odpConfig.equals(config2.odpConfig); + } -// /** -// * Determines if ODP configuration has the minimum amount of information -// */ -// isReady(): boolean { -// return !!this._apiKey && !!this._apiHost; -// } + return true; +} -// /** -// * Detects if there are any changes between the current and incoming ODP Configs -// * @param configToCompare ODP Configuration to check self against for equality -// * @returns Boolean based on if the current ODP Config is equivalent to the incoming ODP Config -// */ -// equals(configToCompare: OdpConfig): boolean { -// return ( -// this._apiHost === configToCompare._apiHost && -// this._apiKey === configToCompare._apiKey && -// this._pixelUrl === configToCompare._pixelUrl && -// checkArrayEquality(this.segmentsToCheck, configToCompare._segmentsToCheck) -// ); -// } -// } +export type OdpIntegrationConfig = OdpNotIntegratedConfig | OdpIntegratedConfig; diff --git a/lib/core/odp/odp_event_api_manager.ts b/lib/core/odp/odp_event_api_manager.ts index 30d5eb7a1..ef32ee0f8 100644 --- a/lib/core/odp/odp_event_api_manager.ts +++ b/lib/core/odp/odp_event_api_manager.ts @@ -18,9 +18,9 @@ import { LogHandler, LogLevel } from '../../modules/logging'; import { OdpEvent } from './odp_event'; import { RequestHandler } from '../../utils/http_request_handler/http'; import { OdpConfig } from './odp_config'; +import { ERROR_MESSAGES } from '../../utils/enums'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; -export const ODP_CONFIG_NOT_READY_MESSAGE = 'ODP config not ready'; /** * Manager for communicating with the Optimizely Data Platform REST API @@ -49,7 +49,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { /** * ODP configuration settings for identifying the target API and segments */ - protected odpConfig?: OdpConfig; + private odpConfig?: OdpConfig; /** * Creates instance to access Optimizely Data Platform (ODP) REST API @@ -81,8 +81,8 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { async sendEvents(events: OdpEvent[]): Promise { let shouldRetry = false; - if (!this.odpConfig?.isReady()) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (${ODP_CONFIG_NOT_READY_MESSAGE})`); + if (!this.odpConfig) { + this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (${ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE})`); return shouldRetry; } @@ -95,7 +95,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { return shouldRetry; } - const { method, endpoint, headers, data } = this.generateRequestData(events); + const { method, endpoint, headers, data } = this.generateRequestData(this.odpConfig, events); let statusCode = 0; try { @@ -125,6 +125,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { protected abstract shouldSendEvents(events: OdpEvent[]): boolean; protected abstract generateRequestData( + odpConfig: OdpConfig, events: OdpEvent[] ): { method: string; diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index 934c2d2fb..f70fc5c4a 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -30,10 +30,9 @@ const MAX_RETRIES = 3; /** * Event dispatcher's execution states */ -export enum STATE { - STOPPED, - RUNNING, - PROCESSING, +export enum Status { + Stopped, + Running, } /** @@ -52,7 +51,7 @@ export interface IOdpEventManager { sendEvent(event: OdpEvent): void; - flush(): void; + flush(retry?: boolean): void; } /** @@ -62,7 +61,7 @@ export abstract class OdpEventManager implements IOdpEventManager { /** * Current state of the event processor */ - state: STATE = STATE.STOPPED; + status: Status = Status.Stopped; /** * Queue for holding all events to be eventually dispatched @@ -80,7 +79,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * ODP configuration settings for identifying the target API and segments * @private */ - private odpConfig: OdpConfig; + private odpConfig?: OdpConfig; /** * REST API Manager used to send the events @@ -148,7 +147,7 @@ export abstract class OdpEventManager implements IOdpEventManager { flushInterval, userAgentParser, }: { - odpConfig: OdpConfig; + odpConfig?: OdpConfig; apiManager: IOdpEventApiManager; logger: LogHandler; clientEngine: string; @@ -164,7 +163,7 @@ export abstract class OdpEventManager implements IOdpEventManager { this.clientEngine = clientEngine; this.clientVersion = clientVersion; this.initParams(batchSize, queueSize, flushInterval); - this.state = STATE.STOPPED; + this.status = Status.Stopped; this.userAgentParser = userAgentParser; if (userAgentParser) { @@ -182,7 +181,9 @@ export abstract class OdpEventManager implements IOdpEventManager { ); } - this.apiManager.updateSettings(odpConfig); + if (odpConfig) { + this.updateSettings(odpConfig); + } } protected abstract initParams( @@ -195,24 +196,35 @@ export abstract class OdpEventManager implements IOdpEventManager { * Update ODP configuration settings. * @param newConfig New configuration to apply */ - updateSettings(newConfig: OdpConfig): void { - this.odpConfig = newConfig; - this.apiManager.updateSettings(newConfig); + updateSettings(odpConfig: OdpConfig): void { + // do nothing if config did not change + if (this.odpConfig && this.odpConfig.equals(odpConfig)) { + return; + } + + this.flush(false); + + this.odpConfig = odpConfig; + this.apiManager.updateSettings(odpConfig); } /** - * Cleans up all pending events; occurs every time the ODP Config is updated. + * Cleans up all pending events; */ - flush(): void { - this.processQueue(true); + flush(retry = true): void { + this.processQueue({ shouldFlush: true, retry }); } /** - * Start processing events in the queue + * Start the event manager */ start(): void { - this.state = STATE.RUNNING; + if (!this.odpConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + return; + } + this.status = Status.Running; this.setNewTimeout(); } @@ -222,9 +234,9 @@ export abstract class OdpEventManager implements IOdpEventManager { async stop(): Promise { this.logger.log(LogLevel.DEBUG, 'Stop requested.'); - await this.processQueue(true); + await this.processQueue({ shouldFlush: true }); - this.state = STATE.STOPPED; + this.status = Status.Stopped; this.logger.log(LogLevel.DEBUG, 'Stopped. Queue Count: %s', this.queue.length); } @@ -283,7 +295,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * @private */ private enqueue(event: OdpEvent): void { - if (this.state === STATE.STOPPED) { + if (this.status === Status.Stopped) { this.logger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.'); return; } @@ -314,38 +326,38 @@ export abstract class OdpEventManager implements IOdpEventManager { * @param shouldFlush Flush all events regardless of available queue event count * @private */ - private processQueue(shouldFlush = false): void { - if (this.state !== STATE.RUNNING) { + private processQueue(options?: { + shouldFlush?: boolean; + retry?: boolean; + }): void { + const { shouldFlush = false, retry = true } = options || {}; + + if (this.status !== Status.Running) { return; } - if (!this.isOdpConfigurationReady()) { + if (!this.odpConfig) { return; } - // Flush interval occurred & queue has items if (shouldFlush) { // clear the queue completely this.clearCurrentTimeout(); - this.state = STATE.PROCESSING; - while (this.queueContainsItems()) { - this.makeAndSend1Batch(); + this.makeAndSend1Batch({ retry }); } } // Check if queue has a full batch available else if (this.queueHasBatches()) { this.clearCurrentTimeout(); - this.state = STATE.PROCESSING; - while (this.queueHasBatches()) { - this.makeAndSend1Batch(); + this.makeAndSend1Batch({ retry }); } } - this.state = STATE.RUNNING; + this.status = Status.Running; this.setNewTimeout(); } @@ -366,14 +378,14 @@ export abstract class OdpEventManager implements IOdpEventManager { if (this.timeoutId !== undefined) { return; } - this.timeoutId = setTimeout(() => this.processQueue(true), this.flushInterval); + this.timeoutId = setTimeout(() => this.processQueue({ shouldFlush: true }), this.flushInterval); } /** * Make a batch and send it to ODP * @private */ - private makeAndSend1Batch(): void { + private makeAndSend1Batch({ retry = true } : { retry: boolean }): void { const batch = new Array(); // remove a batch from the queue @@ -387,8 +399,13 @@ export abstract class OdpEventManager implements IOdpEventManager { } if (batch.length > 0) { + if (!retry) { + this.apiManager.sendEvents(batch); + return; + } + // put sending the event on another event loop - setTimeout(async () => { + setImmediate(async () => { let shouldRetry: boolean; let attemptNumber = 0; do { @@ -417,20 +434,6 @@ export abstract class OdpEventManager implements IOdpEventManager { return this.queue.length > 0; } - /** - * Check if the ODP Configuration is ready and log if not. - * Potentially clear queue if server-side - * @returns True if the ODP configuration is ready otherwise False - * @private - */ - private isOdpConfigurationReady(): boolean { - if (this.odpConfig.isReady()) { - return true; - } - this.discardEventsIfNeeded(); - return false; - } - protected abstract discardEventsIfNeeded(): void; /** diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 6ea9acc93..66f4ec45e 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -20,30 +20,26 @@ import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; import { VuidManager } from '../../plugins/vuid_manager'; -import { OdpConfig } from './odp_config'; +import { OdpConfig, OdpIntegrationConfig, odpIntegrationEquals } from './odp_config'; import { IOdpEventManager } from './odp_event_manager'; import { IOdpSegmentManager } from './odp_segment_manager'; import { OptimizelySegmentOption } from './optimizely_segment_option'; import { invalidOdpDataFound } from './odp_utils'; import { OdpEvent } from './odp_event'; +import { resolvablePromise, ResolvablePromise } from '../../utils/promise/resolvablePromise'; /** * Manager for handling internal all business logic related to * Optimizely Data Platform (ODP) / Advanced Audience Targeting (AAT) */ export interface IOdpManager { - onInit(): Promise; - // initPromise?: Promise; + onReady(): Promise; - enabled: boolean; + isReady(): boolean; - segmentManager: IOdpSegmentManager | undefined; + updateSettings(odpIntegrationConfig: OdpIntegrationConfig): boolean; - eventManager: IOdpEventManager | undefined; - - updateSettings(odpConfig: OdpConfig): boolean; - - close(): void; + stop(): void; fetchQualifiedSegments(userId: string, options?: Array): Promise; @@ -53,11 +49,14 @@ export interface IOdpManager { isVuidEnabled(): boolean; - initializeVuid(): Promise; - getVuid(): string | undefined; } +enum Status { + Running, + Stopped, +} + /** * Orchestrates segments manager, event manager, and ODP configuration */ @@ -65,118 +64,140 @@ export abstract class OdpManager implements IOdpManager { /** * Promise that returns when the OdpManager is finished initializing */ - initPromise?: Promise; + private initPromise: Promise; + + private ready: boolean = false; /** - * Switch to enable/disable ODP Manager functionality + * Promise that resolves when odpConfig becomes available */ - enabled: boolean; + private configPromise: ResolvablePromise; + + private status: Status = Status.Stopped; - vuidManger: VuidManager; - /** * ODP Segment Manager which provides an interface to the remote ODP server (GraphQL API) for audience segments mapping. * It fetches all qualified segments for the given user context and manages the segments cache for all user contexts. */ - segmentManager: IOdpSegmentManager; + private segmentManager: IOdpSegmentManager; /** * ODP Event Manager which provides an interface to the remote ODP server (REST API) for events. * It will queue all pending events (persistent) and send them (in batches of up to 10 events) to the ODP server when possible. */ - eventManager: IOdpEventManager; + private eventManager: IOdpEventManager; /** * Handler for recording execution logs * @protected */ - protected logger: LogHandler = getLogger(); // TODO: Consider making private and moving instantiation to constructor + protected logger: LogHandler; /** * ODP configuration settings for identifying the target API and segments */ - // odpConfig: OdpConfig = new OdpConfig(); // TODO: Consider making private and adding public accessors - odpConfig?: OdpConfig; + odpIntegrationConfig?: OdpIntegrationConfig; // TODO: Consider accepting logger as a parameter and initializing it in constructor instead - private constructor({ - odpConfig, - vuidManager, - segmentManger, + constructor({ + odpIntegrationConfig, + segmentManager, eventManager, + logger, }: { - odpConfig?: OdpConfig; - vuidManager: VuidManager; - segmentManger: IOdpSegmentManager; + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; eventManager: IOdpEventManager; + logger: LogHandler; }) { - this.enabled = !!odpConfig; - this.odpConfig = odpConfig; - this.vuidManger = vuidManager; - this.segmentManager = segmentManger; + this.odpIntegrationConfig = odpIntegrationConfig; + this.segmentManager = segmentManager; this.eventManager = eventManager; + this.logger = logger; - } + this.configPromise = resolvablePromise(); + const readineessDependencies: PromiseLike[] = [this.configPromise]; + if (this.isVuidEnabled()) { + readineessDependencies.push(this.initializeVuid()); + } - // start(): Promise { - // return Promise.resolve(); - // } + this.initPromise = Promise.all(readineessDependencies); - // stop(): Promise { - // return Promise.resolve(); - // } + this.onReady().then(() => { + this.ready = true; + if(this.isVuidEnabled()) { + this.registerVuid(); + } + }); - onInit(): Promise { - return this.initPromise; - } - - /** - * Provides a method to update ODP Manager's ODP Config API Key, API Host, and Audience Segments - */ - updateSettings(odpConfig: OdpConfig): boolean { - if (!odpConfig.integrated) { - this.close(); + if (odpIntegrationConfig) { + this.updateSettings(odpIntegrationConfig); } + } - if (!this.enabled) { - return false; + async start(): Promise { + if (this.status === Status.Running) { + return; } - if (!this.eventManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_MANAGER_UPDATE_SETTINGS_FAILED_EVENT_MANAGER_MISSING); - return false; + if (!this.odpIntegrationConfig) { + return Promise.reject(new Error('cannot start without ODP config')); } - if (!this.segmentManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_MANAGER_UPDATE_SETTINGS_FAILED_SEGMENTS_MANAGER_MISSING); - return false; + if (!this.odpIntegrationConfig.integrated) { + return Promise.reject(new Error('start() called when ODP is not integrated')); } - this.eventManager.flush(); - - const newConfig = new OdpConfig(apiKey, apiHost, pixelUrl, segmentsToCheck); - const configDidUpdate = this.odpConfig.update(newConfig); + this.status = Status.Running; + this.segmentManager.updateSettings(this.odpIntegrationConfig.odpConfig); + this.eventManager.updateSettings(this.odpIntegrationConfig.odpConfig); + this.eventManager.start(); + return Promise.resolve(); + } - if (configDidUpdate) { - this.odpConfig.update(newConfig); - this.segmentManager?.reset(); - return true; + async stop(): Promise { + if (this.status === Status.Stopped) { + return; } + this.status = Status.Stopped; + await this.eventManager.stop(); + } + + onReady(): Promise { + return this.initPromise; + } - return false; + isReady(): boolean { + return this.ready; } /** - * Attempts to stop the current instance of ODP Manager's event manager, if it exists and is running. + * Provides a method to update ODP Manager's ODP Config */ - close(): void { - if (!this.enabled) { - return; + updateSettings(odpIntegrationConfig: OdpIntegrationConfig): boolean { + this.configPromise.resolve(); + + // do nothing if config did not change + if (this.odpIntegrationConfig && odpIntegrationEquals(this.odpIntegrationConfig, odpIntegrationConfig)) { + return false; } - this.eventManager?.stop(); + this.odpIntegrationConfig = odpIntegrationConfig; + + if (odpIntegrationConfig.integrated) { + // already running, just propagate updated config to children; + if (this.status === Status.Running) { + this.segmentManager.updateSettings(odpIntegrationConfig.odpConfig); + this.eventManager.updateSettings(odpIntegrationConfig.odpConfig); + } else { + this.start(); + } + } else { + this.stop(); + } + return true; } /** @@ -187,13 +208,13 @@ export abstract class OdpManager implements IOdpManager { * @returns {Promise} A promise holding either a list of qualified segments or null. */ async fetchQualifiedSegments(userId: string, options: Array = []): Promise { - if (!this.enabled) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED); + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); return null; } - if (!this.segmentManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING); + if (!this.odpIntegrationConfig.integrated) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); return null; } @@ -211,18 +232,13 @@ export abstract class OdpManager implements IOdpManager { * @returns */ identifyUser(userId?: string, vuid?: string): void { - if (!this.enabled) { - this.logger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED); + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); return; } - if (!this.odpConfig.isReady()) { - this.logger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_NOT_INTEGRATED); - return; - } - - if (!this.eventManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_IDENTIFY_FAILED_EVENT_MANAGER_MISSING); + if (!this.odpIntegrationConfig.integrated) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); return; } @@ -245,12 +261,14 @@ export abstract class OdpManager implements IOdpManager { mType = 'fullstack'; } - if (!this.enabled) { - throw new Error(ERROR_MESSAGES.ODP_NOT_ENABLED); + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + return; } - if (!this.odpConfig.isReady()) { - throw new Error(ERROR_MESSAGES.ODP_NOT_INTEGRATED); + if (!this.odpIntegrationConfig.integrated) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); + return; } if (invalidOdpDataFound(data)) { @@ -277,4 +295,21 @@ export abstract class OdpManager implements IOdpManager { * Returns VUID value if it exists */ abstract getVuid(): string | undefined; + + protected initializeVuid(): Promise { + return Promise.resolve(); + } + + private registerVuid() { + const vuid = this.getVuid(); + if (!vuid) { + return; + } + + try { + this.eventManager.registerVuid(vuid); + } catch (e) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_VUID_REGISTRATION_FAILED); + } + } } diff --git a/lib/core/odp/odp_segment_manager.ts b/lib/core/odp/odp_segment_manager.ts index 1d89fd467..b16df1754 100644 --- a/lib/core/odp/odp_segment_manager.ts +++ b/lib/core/odp/odp_segment_manager.ts @@ -40,7 +40,7 @@ export class OdpSegmentManager implements IOdpSegmentManager { * ODP configuration settings in used * @private */ - private odpConfig: OdpConfig; + private odpConfig?: OdpConfig; /** * Holds cached audience segments @@ -69,10 +69,10 @@ export class OdpSegmentManager implements IOdpSegmentManager { private readonly logger: LogHandler; constructor( - odpConfig: OdpConfig, segmentsCache: ICache, odpSegmentApiManager: IOdpSegmentApiManager, - logger?: LogHandler + logger?: LogHandler, + odpConfig?: OdpConfig, ) { this.odpConfig = odpConfig; this._segmentsCache = segmentsCache; @@ -93,11 +93,15 @@ export class OdpSegmentManager implements IOdpSegmentManager { userValue: string, options: Array ): Promise { - const { apiHost: odpApiHost, apiKey: odpApiKey } = this.odpConfig; - - if (!odpApiKey || !odpApiHost) { - this.logger.log(LogLevel.WARNING, ERROR_MESSAGES.FETCH_SEGMENTS_FAILED_INVALID_IDENTIFIER); - return null; + // const { apiHost: odpApiHost, apiKey: odpApiKey } = this.odpConfig; + + // if (!odpApiKey || !odpApiHost) { + // this.logger.log(LogLevel.WARNING, ERROR_MESSAGES.FETCH_SEGMENTS_FAILED_INVALID_IDENTIFIER); + // return null; + // } + if (!this.odpConfig) { + this.logger.log(LogLevel.WARNING, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + return null; } const segmentsToCheck = this.odpConfig.segmentsToCheck; @@ -127,8 +131,8 @@ export class OdpSegmentManager implements IOdpSegmentManager { this.logger.log(LogLevel.DEBUG, `Making a call to ODP server.`); const segments = await this.odpSegmentApiManager.fetchSegments( - odpApiKey, - odpApiHost, + this.odpConfig.apiKey, + this.odpConfig.apiHost, userKey, userValue, segmentsToCheck @@ -164,6 +168,6 @@ export class OdpSegmentManager implements IOdpSegmentManager { */ updateSettings(config: OdpConfig): void { this.odpConfig = config; - this._segmentsCache.reset(); + this.reset(); } } diff --git a/lib/core/project_config/index.ts b/lib/core/project_config/index.ts index b94e3373c..aeedff18c 100644 --- a/lib/core/project_config/index.ts +++ b/lib/core/project_config/index.ts @@ -34,6 +34,7 @@ import { Integration, FeatureVariableValue, } from '../../shared_types'; +import { OdpConfig, OdpIntegrationConfig } from '../odp/odp_config'; interface TryCreatingProjectConfigConfig { // TODO[OASIS-6649]: Don't use object type @@ -90,10 +91,7 @@ export interface ProjectConfig { flagVariationsMap: { [key: string]: Variation[] }; integrations: Integration[]; integrationKeyMap?: { [key: string]: Integration }; - publicKeyForOdp?: string; - hostForOdp?: string; - pixelUrlForOdp?: string; - allSegments: string[]; + odpIntegrationConfig: OdpIntegrationConfig; } const EXPERIMENT_RUNNING_STATUS = 'Running'; @@ -154,19 +152,6 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str projectConfig.audiencesById = keyBy(projectConfig.audiences, 'id'); assign(projectConfig.audiencesById, keyBy(projectConfig.typedAudiences, 'id')); - projectConfig.allSegments = []; - const allSegmentsSet = new Set(); - - Object.keys(projectConfig.audiencesById) - .map(audience => getAudienceSegments(projectConfig.audiencesById[audience])) - .forEach(audienceSegments => { - audienceSegments.forEach(segment => { - allSegmentsSet.add(segment); - }); - }); - - projectConfig.allSegments = Array.from(allSegmentsSet); - projectConfig.attributeKeyMap = keyBy(projectConfig.attributes, 'key'); projectConfig.eventKeyMap = keyBy(projectConfig.events, 'key'); projectConfig.groupIdMap = keyBy(projectConfig.groups, 'id'); @@ -188,6 +173,23 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str }); }); + const allSegmentsSet = new Set(); + + Object.keys(projectConfig.audiencesById) + .map(audience => getAudienceSegments(projectConfig.audiencesById[audience])) + .forEach(audienceSegments => { + audienceSegments.forEach(segment => { + allSegmentsSet.add(segment); + }); + }); + + const allSegments = Array.from(allSegmentsSet); + + let odpIntegrated = false; + let odpApiHost = ''; + let odpApiKey = ''; + let odpPixelUrl = ''; + if (projectConfig.integrations) { projectConfig.integrationKeyMap = keyBy(projectConfig.integrations, 'key'); @@ -197,21 +199,23 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str } if (integration.key === 'odp') { - if (integration.publicKey && !projectConfig.publicKeyForOdp) { - projectConfig.publicKeyForOdp = integration.publicKey; - } - - if (integration.host && !projectConfig.hostForOdp) { - projectConfig.hostForOdp = integration.host; - } - - if (integration.pixelUrl && !projectConfig.pixelUrlForOdp) { - projectConfig.pixelUrlForOdp = integration.pixelUrl; - } + odpIntegrated = true; + odpApiKey = integration.publicKey || ''; + odpApiHost = integration.host || ''; + odpPixelUrl = integration.pixelUrl || ''; } }); } + if (odpIntegrated) { + projectConfig.odpIntegrationConfig = { + integrated: true, + odpConfig: new OdpConfig(odpApiKey, odpApiHost, odpPixelUrl, allSegments), + } + } else { + projectConfig.odpIntegrationConfig = { integrated: false }; + } + projectConfig.experimentKeyMap = keyBy(projectConfig.experiments, 'key'); projectConfig.experimentIdMap = keyBy(projectConfig.experiments, 'id'); diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 46ce0dbe6..c0d62897c 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -131,6 +131,8 @@ const createInstance = function(config: Config): Client | null { logger.info(enums.LOG_MESSAGES.ODP_DISABLED); } + const { clientEngine, clientVersion } = config; + const optimizelyOptions: OptimizelyOptions = { clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, ...config, @@ -142,7 +144,8 @@ const createInstance = function(config: Config): Client | null { : undefined, notificationCenter, isValidInstance, - odpManager: odpExplicitlyOff ? undefined : new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), + odpManager: odpExplicitlyOff ? undefined + : BrowserOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }), }; const optimizely = new Optimizely(optimizelyOptions); diff --git a/lib/index.node.ts b/lib/index.node.ts index 57d72e174..a78599814 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -107,6 +107,8 @@ const createInstance = function(config: Config): Client | null { logger.info(enums.LOG_MESSAGES.ODP_DISABLED); } + const { clientEngine, clientVersion } = config; + const optimizelyOptions = { clientEngine: enums.NODE_CLIENT_ENGINE, ...config, @@ -118,7 +120,8 @@ const createInstance = function(config: Config): Client | null { : undefined, notificationCenter, isValidInstance, - odpManager: odpExplicitlyOff ? undefined : new NodeOdpManager({ logger, odpOptions: config.odpOptions }), + odpManager: odpExplicitlyOff ? undefined + : NodeOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }), }; return new Optimizely(optimizelyOptions); diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts index 6584b46fb..9457052f6 100644 --- a/lib/index.react_native.ts +++ b/lib/index.react_native.ts @@ -103,6 +103,8 @@ const createInstance = function(config: Config): Client | null { logger.info(enums.LOG_MESSAGES.ODP_DISABLED); } + const { clientEngine, clientVersion } = config; + const optimizelyOptions = { clientEngine: enums.REACT_NATIVE_JS_CLIENT_ENGINE, ...config, @@ -120,7 +122,8 @@ const createInstance = function(config: Config): Client | null { : undefined, notificationCenter, isValidInstance: isValidInstance, - odpManager: odpExplicitlyOff ? undefined : new BrowserOdpManager({ logger, odpOptions: config.odpOptions }), + odpManager: odpExplicitlyOff ? undefined + :BrowserOdpManager.createInstance({ logger, odpOptions: config.odpOptions, clientEngine, clientVersion }), }; // If client engine is react, convert it to react native. diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index ef57822c2..531f0db63 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -145,10 +145,6 @@ export default class Optimizely implements Client { this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); - NotificationRegistry.getNotificationCenter(config.sdkKey)?.sendNotifications( - NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE - ); - this.updateOdpSettings(); }); @@ -178,29 +174,11 @@ export default class Optimizely implements Client { const eventProcessorStartedPromise = this.eventProcessor.start(); - const dependentPromises: Array> = [projectConfigManagerReadyPromise, eventProcessorStartedPromise]; - - if (config.odpManager?.initPromise) { - dependentPromises.push(config.odpManager.initPromise); - } - - this.readyPromise = Promise.all(dependentPromises).then(promiseResults => { - // If no odpManager exists yet, creates a new one - if (config.odpManager != null) { - this.odpManager = config.odpManager; - this.odpManager.eventManager?.start(); - this.updateOdpSettings(); - const sdkKey = this.projectConfigManager.getConfig()?.sdkKey; - if (sdkKey != null) { - NotificationRegistry.getNotificationCenter( - sdkKey, - this.logger - )?.addNotificationListener(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, () => this.updateOdpSettings()); - } else { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE); - } - } - + this.readyPromise = Promise.all([ + projectConfigManagerReadyPromise, + eventProcessorStartedPromise, + config.odpManager ? config.odpManager.onReady() : Promise.resolve(), + ]).then(promiseResults => { // Only return status from project config promise because event processor promise does not return any status. return promiseResults[0]; }); @@ -209,6 +187,15 @@ export default class Optimizely implements Client { this.nextReadyTimeoutId = 0; } + + /** + * Returns the project configuration retrieved from projectConfigManager + * @return {projectConfig.ProjectConfig} + */ + getProjectConfig(): projectConfig.ProjectConfig | null { + return this.projectConfigManager.getConfig(); + } + /** * Returns a truthy value if this instance currently has a valid project config * object, and the initial configuration object that was passed into the @@ -1317,7 +1304,7 @@ export default class Optimizely implements Client { close(): Promise<{ success: boolean; reason?: string }> { try { if (this.odpManager) { - this.odpManager.close(); + this.odpManager.stop(); } this.notificationCenter.clearAllNotificationListeners(); @@ -1454,16 +1441,9 @@ export default class Optimizely implements Client { * null if provided inputs are invalid */ createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { - let userIdentifier; - - if (this.odpManager?.isVuidEnabled() && !userId) { - userIdentifier = userId || this.getVuid(); - } else { - userIdentifier = userId; - } + let userIdentifier = userId || this.odpManager?.getVuid(); if ( - userIdentifier === null || userIdentifier === undefined || !this.validateInputs({ user_id: userIdentifier }, attributes) ) { @@ -1684,15 +1664,13 @@ export default class Optimizely implements Client { */ private updateOdpSettings(): void { const projectConfig = this.projectConfigManager.getConfig(); - if (this.odpManager != null && projectConfig != null) { - this.odpManager.updateSettings( - new OdpConfig( - projectConfig.publicKeyForOdp, - projectConfig.hostForOdp, - projectConfig.pixelUrlForOdp, - projectConfig.allSegments - ) - ); + + if (!projectConfig) { + return; + } + + if (this.odpManager) { + this.odpManager.updateSettings(projectConfig.odpIntegrationConfig) } } @@ -1744,12 +1722,17 @@ export default class Optimizely implements Client { } } + private isOdpIntegrated(): boolean { + return this.projectConfigManager.getConfig()?.odpIntegrationConfig?.integrated ?? false; + } + /** * Identifies user with ODP server in a fire-and-forget manner. + * Should be called only after the instance is ready * @param {string} userId */ public identifyUser(userId: string): void { - if (this.odpManager && this.odpManager.enabled) { + if (this.odpManager && this.isOdpIntegrated()) { this.odpManager.identifyUser(userId); } } @@ -1767,12 +1750,7 @@ export default class Optimizely implements Client { if (!this.odpManager) { return null; } - - if (!this.odpManager.enabled) { - this.logger.error(ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_FAILED_ODP_MANAGER_MISSING); - return null; - } - + return await this.odpManager.fetchQualifiedSegments(userId, options); } diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index de3930290..201541a71 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -64,7 +64,9 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { this.forcedDecisionsMap = {}; if (shouldIdentifyUser) { - this.identifyUser(); + this.optimizely.onReady().then(() => { + this.identifyUser(); + }); } } diff --git a/lib/plugins/odp/event_api_manager/index.browser.ts b/lib/plugins/odp/event_api_manager/index.browser.ts index 592978f63..9f9a78ab8 100644 --- a/lib/plugins/odp/event_api_manager/index.browser.ts +++ b/lib/plugins/odp/event_api_manager/index.browser.ts @@ -1,7 +1,7 @@ import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; import { LogLevel } from '../../../modules/logging'; -import { ODP_CONFIG_NOT_READY_MESSAGE } from '../../../core/odp/odp_event_api_manager'; +import { OdpConfig, OdpIntegrationConfig } from '../../../core/odp/odp_config'; const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; @@ -16,31 +16,19 @@ export class BrowserOdpEventApiManager extends OdpEventApiManager { return false; } - private getPixelApiEndpoint(): string { - if (!this.odpConfig?.isReady()) { - throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); - } - const pixelUrl = this.odpConfig.pixelUrl; + private getPixelApiEndpoint(odpConfig: OdpConfig): string { + const pixelUrl = odpConfig.pixelUrl; const pixelApiEndpoint = new URL(pixelApiPath, pixelUrl).href; return pixelApiEndpoint; } protected generateRequestData( + odpConfig: OdpConfig, events: OdpEvent[] ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { - // the caller should ensure odpConfig is ready before calling - if (!this.odpConfig?.isReady()) { - this.getLogger().log(LogLevel.ERROR, ODP_CONFIG_NOT_READY_MESSAGE); - throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); - } - - // this cannot be cached cause OdpConfig is mutable - // and can be updated in place and it is done so in odp - // manager. We should make OdpConfig immutable and - // refacator later - const pixelApiEndpoint = this.getPixelApiEndpoint(); + const pixelApiEndpoint = this.getPixelApiEndpoint(odpConfig); - const apiKey = this.odpConfig.apiKey; + const apiKey = odpConfig.apiKey; const method = 'GET'; const event = events[0]; const url = new URL(pixelApiEndpoint); diff --git a/lib/plugins/odp/event_api_manager/index.node.ts b/lib/plugins/odp/event_api_manager/index.node.ts index 1d04bc9d3..aeaf57020 100644 --- a/lib/plugins/odp/event_api_manager/index.node.ts +++ b/lib/plugins/odp/event_api_manager/index.node.ts @@ -1,23 +1,18 @@ +import { OdpConfig, OdpIntegrationConfig } from '../../../core/odp/odp_config'; import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; import { LogLevel } from '../../../modules/logging'; -import { ODP_CONFIG_NOT_READY_MESSAGE } from '../../../core/odp/odp_event_api_manager'; export class NodeOdpEventApiManager extends OdpEventApiManager { protected shouldSendEvents(events: OdpEvent[]): boolean { return true; } protected generateRequestData( + odpConfig: OdpConfig, events: OdpEvent[] ): { method: string; endpoint: string; headers: { [key: string]: string }; data: string } { - // the caller should ensure odpConfig is ready before calling - if (!this.odpConfig?.isReady()) { - this.getLogger().log(LogLevel.ERROR, ODP_CONFIG_NOT_READY_MESSAGE); - throw new Error(ODP_CONFIG_NOT_READY_MESSAGE); - } - - const apiHost = this.odpConfig.apiHost; - const apiKey = this.odpConfig.apiKey; + + const { apiHost, apiKey } = odpConfig; return { method: 'POST', diff --git a/lib/plugins/odp_manager/index.browser.ts b/lib/plugins/odp_manager/index.browser.ts index 3f933d870..3051c8f28 100644 --- a/lib/plugins/odp_manager/index.browser.ts +++ b/lib/plugins/odp_manager/index.browser.ts @@ -35,127 +35,48 @@ import { VuidManager } from './../vuid_manager/index'; import { OdpManager } from '../../core/odp/odp_manager'; import { OdpEvent } from '../../core/odp/odp_event'; -import { OdpOptions } from '../../shared_types'; +import { IOdpEventManager, OdpOptions } from '../../shared_types'; import { BrowserOdpEventApiManager } from '../odp/event_api_manager/index.browser'; import { BrowserOdpEventManager } from '../odp/event_manager/index.browser'; -import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; +import { IOdpSegmentManager, OdpSegmentManager } from '../../core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../../core/odp/odp_segment_api_manager'; -import { OdpConfig } from 'lib/core/odp/odp_config'; +import { OdpConfig, OdpIntegrationConfig } from '../../core/odp/odp_config'; interface BrowserOdpManagerConfig { + clientEngine?: string, + clientVersion?: string, logger?: LogHandler; odpOptions?: OdpOptions; + odpIntegrationConfig?: OdpIntegrationConfig; } // Client-side Browser Plugin for ODP Manager export class BrowserOdpManager extends OdpManager { static cache = new BrowserAsyncStorageCache(); + vuidManager?: VuidManager; vuid?: string; - // constructor({ logger, odpOptions }: BrowserOdpManagerConfig) { - // super(); - - // this.logger = logger || getLogger(); - - // if (odpOptions?.disabled) { - // this.initPromise = Promise.resolve(); - // this.enabled = false; - // this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); - // return; - // } - - // const browserClientEngine = JAVASCRIPT_CLIENT_ENGINE; - // const browserClientVersion = CLIENT_VERSION; - - // let customSegmentRequestHandler; - - // if (odpOptions?.segmentsRequestHandler) { - // customSegmentRequestHandler = odpOptions.segmentsRequestHandler; - // } else { - // customSegmentRequestHandler = new BrowserRequestHandler( - // this.logger, - // odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS - // ); - // } - - // // Set up Segment Manager (Audience Segments GraphQL API Interface) - // if (odpOptions?.segmentManager) { - // this.segmentManager = odpOptions.segmentManager; - // this.segmentManager.updateSettings(this.odpConfig); - // } else { - // this.segmentManager = new OdpSegmentManager( - // this.odpConfig, - // odpOptions?.segmentsCache || - // new BrowserLRUCache({ - // maxSize: odpOptions?.segmentsCacheSize, - // timeout: odpOptions?.segmentsCacheTimeout, - // }), - // new OdpSegmentApiManager(customSegmentRequestHandler, this.logger) - // ); - // } - - // let customEventRequestHandler; - - // if (odpOptions?.eventRequestHandler) { - // customEventRequestHandler = odpOptions.eventRequestHandler; - // } else { - // customEventRequestHandler = new BrowserRequestHandler( - // this.logger, - // odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS - // ); - // } - - // // Set up Events Manager (Events REST API Interface) - // if (odpOptions?.eventManager) { - // this.eventManager = odpOptions.eventManager; - // this.eventManager.updateSettings(this.odpConfig); - // } else { - // this.eventManager = new BrowserOdpEventManager({ - // odpConfig: this.odpConfig, - // apiManager: new BrowserOdpEventApiManager(customEventRequestHandler, this.logger), - // logger: this.logger, - // clientEngine: browserClientEngine, - // clientVersion: browserClientVersion, - // flushInterval: odpOptions?.eventFlushInterval, - // batchSize: odpOptions?.eventBatchSize, - // queueSize: odpOptions?.eventQueueSize, - // userAgentParser: odpOptions?.userAgentParser, - // }); - // } - - // this.eventManager!.start(); - - // this.initPromise = this.initializeVuid(BrowserOdpManager.cache).catch(e => { - // this.logger.log(this.enabled ? LogLevel.ERROR : LogLevel.DEBUG, e); - // }); - // } - - private constructor({ - odpConfig, - vuidManager, - segmentManger, - eventManager, - }: { - odpConfig?: OdpConfig; - vuidManager: VuidManager; - segmentManger: OdpSegmentManager; - eventManager: BrowserOdpEventManager; + constructor(options: { + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; + eventManager: IOdpEventManager; + logger: LogHandler; }) { - super(); + super(options); } - static createInstance({ logger, odpOptions }: BrowserOdpManagerConfig): BrowserOdpManager { + static createInstance({ + logger, odpOptions, odpIntegrationConfig, clientEngine, clientVersion + }: BrowserOdpManagerConfig): BrowserOdpManager { logger = logger || getLogger(); - if (odpOptions?.disabled) { - this.initPromise = Promise.resolve(); - this.enabled = false; - this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); - return; - } + clientEngine = clientEngine || JAVASCRIPT_CLIENT_ENGINE; + clientVersion = clientVersion || CLIENT_VERSION; - const browserClientEngine = JAVASCRIPT_CLIENT_ENGINE; - const browserClientVersion = CLIENT_VERSION; + let odpConfig : OdpConfig | undefined = undefined; + if (odpIntegrationConfig?.integrated) { + odpConfig = odpIntegrationConfig.odpConfig; + } let customSegmentRequestHandler; @@ -163,24 +84,25 @@ export class BrowserOdpManager extends OdpManager { customSegmentRequestHandler = odpOptions.segmentsRequestHandler; } else { customSegmentRequestHandler = new BrowserRequestHandler( - this.logger, + logger, odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS ); } - // Set up Segment Manager (Audience Segments GraphQL API Interface) + let segmentManager: IOdpSegmentManager; + if (odpOptions?.segmentManager) { - this.segmentManager = odpOptions.segmentManager; - this.segmentManager.updateSettings(this.odpConfig); + segmentManager = odpOptions.segmentManager; } else { - this.segmentManager = new OdpSegmentManager( - this.odpConfig, + segmentManager = new OdpSegmentManager( odpOptions?.segmentsCache || new BrowserLRUCache({ maxSize: odpOptions?.segmentsCacheSize, timeout: odpOptions?.segmentsCacheTimeout, }), - new OdpSegmentApiManager(customSegmentRequestHandler, this.logger) + new OdpSegmentApiManager(customSegmentRequestHandler, logger), + logger, + odpConfig ); } @@ -190,22 +112,22 @@ export class BrowserOdpManager extends OdpManager { customEventRequestHandler = odpOptions.eventRequestHandler; } else { customEventRequestHandler = new BrowserRequestHandler( - this.logger, + logger, odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS ); } - // Set up Events Manager (Events REST API Interface) + let eventManager: IOdpEventManager; + if (odpOptions?.eventManager) { - this.eventManager = odpOptions.eventManager; - this.eventManager.updateSettings(this.odpConfig); + eventManager = odpOptions.eventManager; } else { - this.eventManager = new BrowserOdpEventManager({ - odpConfig: this.odpConfig, - apiManager: new BrowserOdpEventApiManager(customEventRequestHandler, this.logger), - logger: this.logger, - clientEngine: browserClientEngine, - clientVersion: browserClientVersion, + eventManager = new BrowserOdpEventManager({ + odpConfig, + apiManager: new BrowserOdpEventApiManager(customEventRequestHandler, logger), + logger: logger, + clientEngine, + clientVersion, flushInterval: odpOptions?.eventFlushInterval, batchSize: odpOptions?.eventBatchSize, queueSize: odpOptions?.eventQueueSize, @@ -213,34 +135,21 @@ export class BrowserOdpManager extends OdpManager { }); } - this.eventManager!.start(); - - this.initPromise = this.initializeVuid(BrowserOdpManager.cache).catch(e => { - this.logger.log(this.enabled ? LogLevel.ERROR : LogLevel.DEBUG, e); + return new BrowserOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, }); } /** - * Upon initializing BrowserOdpManager, accesses or creates new VUID from Browser cache and registers it via the Event Manager - * @private + * @override + * accesses or creates new VUID from Browser cache and registers it via the Event Manager */ - private async initializeVuid(cache: PersistentKeyValueCache): Promise { - const vuidManager = await VuidManager.instance(cache); + protected async initializeVuid(): Promise { + const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); this.vuid = vuidManager.vuid; - this.registerVuid(this.vuid); - } - - private registerVuid(vuid: string) { - if (!this.eventManager) { - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING); - return; - } - - try { - this.eventManager.registerVuid(vuid); - } catch (e) { - this.logger.log(this.enabled ? LogLevel.ERROR : LogLevel.DEBUG, ERROR_MESSAGES.ODP_VUID_REGISTRATION_FAILED); - } } /** diff --git a/lib/plugins/odp_manager/index.node.ts b/lib/plugins/odp_manager/index.node.ts index 4f01ededc..bdd57f1ad 100644 --- a/lib/plugins/odp_manager/index.node.ts +++ b/lib/plugins/odp_manager/index.node.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,6 @@ import { ServerLRUCache } from './../../utils/lru_cache/server_lru_cache'; import { getLogger, LogHandler, LogLevel } from '../../modules/logging'; import { - LOG_MESSAGES, NODE_CLIENT_ENGINE, CLIENT_VERSION, REQUEST_TIMEOUT_ODP_EVENTS_MS, @@ -28,15 +27,19 @@ import { } from '../../utils/enums'; import { OdpManager } from '../../core/odp/odp_manager'; -import { OdpOptions } from '../../shared_types'; +import { IOdpEventManager, OdpOptions } from '../../shared_types'; import { NodeOdpEventApiManager } from '../odp/event_api_manager/index.node'; import { NodeOdpEventManager } from '../odp/event_manager/index.node'; -import { OdpSegmentManager } from '../../core/odp/odp_segment_manager'; +import { IOdpSegmentManager, OdpSegmentManager } from '../../core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../../core/odp/odp_segment_api_manager'; +import { OdpConfig, OdpIntegrationConfig } from '../../core/odp/odp_config'; interface NodeOdpManagerConfig { + clientEngine?: string, + clientVersion?: string, logger?: LogHandler; odpOptions?: OdpOptions; + odpIntegrationConfig?: OdpIntegrationConfig; } /** @@ -44,20 +47,27 @@ interface NodeOdpManagerConfig { * Note: As this is still a work-in-progress. Please avoid using the Node ODP Manager. */ export class NodeOdpManager extends OdpManager { - constructor({ logger, odpOptions }: NodeOdpManagerConfig) { - super(); + constructor(options: { + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; + eventManager: IOdpEventManager; + logger: LogHandler; + }) { + super(options); + } - this.logger = logger || getLogger(); + static createInstance({ + logger, odpOptions, odpIntegrationConfig, clientEngine, clientVersion + }: NodeOdpManagerConfig): NodeOdpManager { + logger = logger || getLogger(); - if (odpOptions?.disabled) { - this.initPromise = Promise.resolve(); - this.enabled = false; - this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED); - return; - } + clientEngine = clientEngine || NODE_CLIENT_ENGINE; + clientVersion = clientVersion || CLIENT_VERSION; - const nodeClientEngine = NODE_CLIENT_ENGINE; - const nodeClientVersion = CLIENT_VERSION; + let odpConfig : OdpConfig | undefined = undefined; + if (odpIntegrationConfig?.integrated) { + odpConfig = odpIntegrationConfig.odpConfig; + } let customSegmentRequestHandler; @@ -65,24 +75,25 @@ export class NodeOdpManager extends OdpManager { customSegmentRequestHandler = odpOptions.segmentsRequestHandler; } else { customSegmentRequestHandler = new NodeRequestHandler( - this.logger, + logger, odpOptions?.segmentsApiTimeout || REQUEST_TIMEOUT_ODP_SEGMENTS_MS ); } - // Set up Segment Manager (Audience Segments GraphQL API Interface) + let segmentManager: IOdpSegmentManager; + if (odpOptions?.segmentManager) { - this.segmentManager = odpOptions.segmentManager; - this.segmentManager.updateSettings(this.odpConfig); + segmentManager = odpOptions.segmentManager; } else { - this.segmentManager = new OdpSegmentManager( - this.odpConfig, + segmentManager = new OdpSegmentManager( odpOptions?.segmentsCache || new ServerLRUCache({ maxSize: odpOptions?.segmentsCacheSize, timeout: odpOptions?.segmentsCacheTimeout, }), - new OdpSegmentApiManager(customSegmentRequestHandler, this.logger) + new OdpSegmentApiManager(customSegmentRequestHandler, logger), + logger, + odpConfig ); } @@ -92,31 +103,35 @@ export class NodeOdpManager extends OdpManager { customEventRequestHandler = odpOptions.eventRequestHandler; } else { customEventRequestHandler = new NodeRequestHandler( - this.logger, + logger, odpOptions?.eventApiTimeout || REQUEST_TIMEOUT_ODP_EVENTS_MS ); } - // Set up Events Manager (Events REST API Interface) + let eventManager: IOdpEventManager; + if (odpOptions?.eventManager) { - this.eventManager = odpOptions.eventManager; - this.eventManager.updateSettings(this.odpConfig); + eventManager = odpOptions.eventManager; } else { - this.eventManager = new NodeOdpEventManager({ - odpConfig: this.odpConfig, - apiManager: new NodeOdpEventApiManager(customEventRequestHandler, this.logger), - logger: this.logger, - clientEngine: nodeClientEngine, - clientVersion: nodeClientVersion, + eventManager = new NodeOdpEventManager({ + odpConfig, + apiManager: new NodeOdpEventApiManager(customEventRequestHandler, logger), + logger: logger, + clientEngine, + clientVersion, flushInterval: odpOptions?.eventFlushInterval, batchSize: odpOptions?.eventBatchSize, queueSize: odpOptions?.eventQueueSize, + userAgentParser: odpOptions?.userAgentParser, }); } - this.eventManager.start(); - - this.initPromise = Promise.resolve(); + return new NodeOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + }); } public isVuidEnabled(): boolean { diff --git a/lib/shared_types.ts b/lib/shared_types.ts index 4af80fa13..361f293d5 100644 --- a/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -37,6 +37,7 @@ import { IOdpEventManager } from './core/odp/odp_event_manager'; import { IOdpManager } from './core/odp/odp_manager'; import { IUserAgentParser } from './core/odp/user_agent_parser'; import PersistentCache from './plugins/key_value_cache/persistentKeyValueCache'; +import { ProjectConfig } from './core/project_config'; export interface BucketerParams { experimentId: string; @@ -369,6 +370,7 @@ export interface Client { onReady(options?: { timeout?: number }): Promise<{ success: boolean; reason?: string }>; close(): Promise<{ success: boolean; reason?: string }>; sendOdpEvent(action: string, type?: string, identifiers?: Map, data?: Map): void; + getProjectConfig(): ProjectConfig | null; } export interface ActivateListenerPayload extends ListenerPayload { diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts index 95fe4f4ed..3f7bee937 100644 --- a/lib/utils/enums/index.ts +++ b/lib/utils/enums/index.ts @@ -31,7 +31,6 @@ export const ERROR_MESSAGES = { DATAFILE_AND_SDK_KEY_MISSING: '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely', EXPERIMENT_KEY_NOT_IN_DATAFILE: '%s: Experiment key %s is not in datafile.', FEATURE_NOT_IN_DATAFILE: '%s: Feature key %s is not in datafile.', - FETCH_SEGMENTS_FAILED_INVALID_IDENTIFIER: '%s: Audience segments fetch failed. (invalid identifier)', FETCH_SEGMENTS_FAILED_NETWORK_ERROR: '%s: Audience segments fetch failed. (network error)', FETCH_SEGMENTS_FAILED_DECODE_ERROR: '%s: Audience segments fetch failed. (decode error)', IMPROPERLY_FORMATTED_EXPERIMENT: '%s: Experiment key %s is improperly formatted.', @@ -56,6 +55,7 @@ export const ERROR_MESSAGES = { NO_DATAFILE_SPECIFIED: '%s: No datafile specified. Cannot start optimizely.', NO_JSON_PROVIDED: '%s: No JSON object to validate against schema.', NO_VARIATION_FOR_EXPERIMENT_KEY: '%s: No variation key %s defined in datafile for experiment %s.', + ODP_CONFIG_NOT_AVAILABLE: '%s: ODP is not integrated to the project.', ODP_EVENT_FAILED: 'ODP event send failed.', ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING: '%s: ODP unable to fetch qualified segments (Segments Manager not initialized).', @@ -79,8 +79,6 @@ export const ERROR_MESSAGES = { '%s: ODP send event %s was not dispatched (Event Manager not instantiated).', ODP_SEND_EVENT_FAILED_UID_MISSING: '%s: ODP send event %s was not dispatched (No valid user identifier provided).', ODP_SEND_EVENT_FAILED_VUID_MISSING: '%s: ODP send event %s was not dispatched (Unable to fetch VUID).', - ODP_SDK_KEY_MISSING_NOTIFICATION_CENTER_FAILURE: - '%s: You must provide an sdkKey. Cannot start Notification Center for ODP Integration.', ODP_VUID_INITIALIZATION_FAILED: '%s: ODP VUID initialization failed.', ODP_VUID_REGISTRATION_FAILED: '%s: ODP VUID failed to be registered.', ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING: '%s: ODP register vuid failed. (Event Manager not instantiated).', diff --git a/lib/utils/promise/resolvablePromise.ts b/lib/utils/promise/resolvablePromise.ts new file mode 100644 index 000000000..e5ec5f864 --- /dev/null +++ b/lib/utils/promise/resolvablePromise.ts @@ -0,0 +1,18 @@ +const noop = () => {}; + +export type ResolvablePromise = { + promise: Promise; + resolve: (value: T | PromiseLike) => void; + reject: (reason?: any) => void; + then: Promise['then']; +}; + +export function resolvablePromise(): ResolvablePromise { + let resolve: (value: T | PromiseLike) => void = noop; + let reject: (reason?: any) => void = noop; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject, then: promise.then.bind(promise) }; +} From cd5dbad11e9bbffc1abb05fc29c3c3635faf7c8d Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 29 Mar 2024 02:37:26 +0600 Subject: [PATCH 03/24] fix segment manager tests --- tests/odpSegmentManager.spec.ts | 73 ++++++++++++++++----------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/tests/odpSegmentManager.spec.ts b/tests/odpSegmentManager.spec.ts index 5deea4348..b43f1b5d6 100644 --- a/tests/odpSegmentManager.spec.ts +++ b/tests/odpSegmentManager.spec.ts @@ -45,8 +45,6 @@ describe('OdpSegmentManager', () => { const mockLogHandler = mock(); const mockRequestHandler = mock(); - let manager: OdpSegmentManager; - let odpConfig: OdpConfig; const apiManager = new MockOdpSegmentApiManager(instance(mockRequestHandler), instance(mockLogHandler)); let options: Array = []; @@ -57,6 +55,13 @@ describe('OdpSegmentManager', () => { const validTestOdpConfig = new OdpConfig('valid-key', 'host', 'pixel-url', ['new-customer']); const invalidTestOdpConfig = new OdpConfig('invalid-key', 'host', 'pixel-url', ['new-customer']); + const getSegmentsCache = () => { + return new LRUCache({ + maxSize: 1000, + timeout: 1000, + }); + } + beforeEach(() => { resetCalls(mockLogHandler); resetCalls(mockRequestHandler); @@ -64,93 +69,87 @@ describe('OdpSegmentManager', () => { const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com'; const PIXEL_URL = 'https://odp.pixel.com'; - odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); - const segmentsCache = new LRUCache({ - maxSize: 1000, - timeout: 1000, - }); - - manager = new OdpSegmentManager(odpConfig, segmentsCache, apiManager); }); it('should fetch segments successfully on cache miss.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, '123', ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, '123', ['a']); const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); }); it('should fetch segments successfully on cache hit.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, userValue, ['a']); const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['a']); }); - it('should throw an error when fetching segments returns an error.', async () => { - odpConfig.update(invalidTestOdpConfig); + it('should return null when fetching segments returns an error.', async () => { + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, invalidTestOdpConfig); const segments = await manager.fetchQualifiedSegments(userKey, userValue, []); expect(segments).toBeNull; }); it('should ignore the cache if the option enum is included in the options array.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, userValue, ['a']); options = [OptimizelySegmentOption.IGNORE_CACHE]; const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); - expect(cacheCount()).toBe(1); + expect(cacheCount(manager)).toBe(1); }); it('should ignore the cache if the option string is included in the options array.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager,userKey, userValue, ['a']); // @ts-ignore options = ['IGNORE_CACHE']; const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); - expect(cacheCount()).toBe(1); + expect(cacheCount(manager)).toBe(1); }); it('should reset the cache if the option enum is included in the options array.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); - setCache(userKey, '123', ['a']); - setCache(userKey, '456', ['a']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, userValue, ['a']); + setCache(manager, userKey, '123', ['a']); + setCache(manager, userKey, '456', ['a']); options = [OptimizelySegmentOption.RESET_CACHE]; const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); - expect(peekCache(userKey, userValue)).toEqual(segments); - expect(cacheCount()).toBe(1); + expect(peekCache(manager, userKey, userValue)).toEqual(segments); + expect(cacheCount(manager)).toBe(1); }); it('should reset the cache if the option string is included in the options array.', async () => { - odpConfig.update(validTestOdpConfig); - setCache(userKey, userValue, ['a']); - setCache(userKey, '123', ['a']); - setCache(userKey, '456', ['a']); - // @ts-ignore + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + setCache(manager, userKey, userValue, ['a']); + setCache(manager, userKey, '123', ['a']); + setCache(manager, userKey, '456', ['a']); + // @ts-ignore options = ['RESET_CACHE']; const segments = await manager.fetchQualifiedSegments(userKey, userValue, options); expect(segments).toEqual(['new-customer']); - expect(peekCache(userKey, userValue)).toEqual(segments); - expect(cacheCount()).toBe(1); + expect(peekCache(manager, userKey, userValue)).toEqual(segments); + expect(cacheCount(manager)).toBe(1); }); it('should make a valid cache key.', () => { + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); expect('vuid-$-test-user').toBe(manager.makeCacheKey(userKey, userValue)); }); // Utility Functions - function setCache(userKey: string, userValue: string, value: string[]) { + function setCache(manager: OdpSegmentManager, userKey: string, userValue: string, value: string[]) { const cacheKey = manager.makeCacheKey(userKey, userValue); manager.segmentsCache.save({ key: cacheKey, @@ -158,10 +157,10 @@ describe('OdpSegmentManager', () => { }); } - function peekCache(userKey: string, userValue: string): string[] | null { + function peekCache(manager: OdpSegmentManager, userKey: string, userValue: string): string[] | null { const cacheKey = manager.makeCacheKey(userKey, userValue); return (manager.segmentsCache as LRUCache).peek(cacheKey); } - const cacheCount = () => (manager.segmentsCache as LRUCache).map.size; + const cacheCount = (manager: OdpSegmentManager) => (manager.segmentsCache as LRUCache).map.size; }); From cf6f045c69bbf21f28c62ef0a62e04b76f09a02e Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 29 Mar 2024 15:14:20 +0600 Subject: [PATCH 04/24] fix identify user --- lib/optimizely_user_context/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index 201541a71..db6a96932 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -64,8 +64,10 @@ export default class OptimizelyUserContext implements IOptimizelyUserContext { this.forcedDecisionsMap = {}; if (shouldIdentifyUser) { - this.optimizely.onReady().then(() => { - this.identifyUser(); + this.optimizely.onReady().then(({ success }) => { + if (success) { + this.identifyUser(); + } }); } } From 7dfcbcdf956d139c4a707306aef829cb47c195c5 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 29 Mar 2024 16:47:50 +0600 Subject: [PATCH 05/24] update event api manager --- lib/core/odp/odp_event_api_manager.ts | 25 +------- lib/core/odp/odp_event_manager.ts | 9 ++- lib/core/odp/odp_manager.ts | 1 - tests/odpEventApiManager.spec.ts | 31 +++------- tests/odpEventManager.spec.ts | 86 ++++++++++++++------------- 5 files changed, 62 insertions(+), 90 deletions(-) diff --git a/lib/core/odp/odp_event_api_manager.ts b/lib/core/odp/odp_event_api_manager.ts index ef32ee0f8..20067a178 100644 --- a/lib/core/odp/odp_event_api_manager.ts +++ b/lib/core/odp/odp_event_api_manager.ts @@ -26,8 +26,7 @@ const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; * Manager for communicating with the Optimizely Data Platform REST API */ export interface IOdpEventApiManager { - sendEvents(events: OdpEvent[]): Promise; - updateSettings(odpConfig: OdpConfig): void; + sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise; } /** @@ -46,11 +45,6 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { */ private readonly requestHandler: RequestHandler; - /** - * ODP configuration settings for identifying the target API and segments - */ - private odpConfig?: OdpConfig; - /** * Creates instance to access Optimizely Data Platform (ODP) REST API * @param requestHandler Desired request handler for testing @@ -61,14 +55,6 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { this.logger = logger; } - /** - * Updates odpConfig of the api manager instance - * @param odpConfig - */ - updateSettings(odpConfig: OdpConfig): void { - this.odpConfig = odpConfig; - } - getLogger(): LogHandler { return this.logger; } @@ -78,14 +64,9 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { * @param events ODP events to send * @returns Retry is true - if network or server error (5xx), otherwise false */ - async sendEvents(events: OdpEvent[]): Promise { + async sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise { let shouldRetry = false; - if (!this.odpConfig) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (${ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE})`); - return shouldRetry; - } - if (events.length === 0) { this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (no events)`); return shouldRetry; @@ -95,7 +76,7 @@ export abstract class OdpEventApiManager implements IOdpEventApiManager { return shouldRetry; } - const { method, endpoint, headers, data } = this.generateRequestData(this.odpConfig, events); + const { method, endpoint, headers, data } = this.generateRequestData(odpConfig, events); let statusCode = 0; try { diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index f70fc5c4a..6799c8426 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -157,7 +157,6 @@ export abstract class OdpEventManager implements IOdpEventManager { flushInterval?: number; userAgentParser?: IUserAgentParser; }) { - this.odpConfig = odpConfig; this.apiManager = apiManager; this.logger = logger; this.clientEngine = clientEngine; @@ -336,10 +335,6 @@ export abstract class OdpEventManager implements IOdpEventManager { return; } - if (!this.odpConfig) { - return; - } - if (shouldFlush) { // clear the queue completely this.clearCurrentTimeout(); @@ -457,4 +452,8 @@ export abstract class OdpEventManager implements IOdpEventManager { protected getLogger(): LogHandler { return this.logger; } + + getQueue(): OdpEvent[] { + return this.queue; + } } diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 66f4ec45e..4b9db3b12 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -110,7 +110,6 @@ export abstract class OdpManager implements IOdpManager { eventManager: IOdpEventManager; logger: LogHandler; }) { - this.odpIntegrationConfig = odpIntegrationConfig; this.segmentManager = segmentManager; this.eventManager = eventManager; this.logger = logger; diff --git a/tests/odpEventApiManager.spec.ts b/tests/odpEventApiManager.spec.ts index bfe813af6..2d06e9570 100644 --- a/tests/odpEventApiManager.spec.ts +++ b/tests/odpEventApiManager.spec.ts @@ -57,7 +57,6 @@ describe('NodeOdpEventApiManager', () => { const managerInstance = () => { const manager = new NodeOdpEventApiManager(instance(mockRequestHandler), instance(mockLogger)); - manager.updateSettings(odpConfig); return manager; } @@ -78,7 +77,7 @@ describe('NodeOdpEventApiManager', () => { ); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(ODP_EVENTS); + const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); expect(shouldRetry).toBe(false); verify(mockLogger.log(anything(), anyString())).never(); @@ -90,7 +89,7 @@ describe('NodeOdpEventApiManager', () => { ); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(ODP_EVENTS); + const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); expect(shouldRetry).toBe(false); verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (400)')).once(); @@ -102,7 +101,7 @@ describe('NodeOdpEventApiManager', () => { ); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(ODP_EVENTS); + const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); expect(shouldRetry).toBe(true); verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (500)')).once(); @@ -115,13 +114,13 @@ describe('NodeOdpEventApiManager', () => { }); const manager = managerInstance(); - const shouldRetry = await manager.sendEvents(ODP_EVENTS); + const shouldRetry = await manager.sendEvents(odpConfig, ODP_EVENTS); expect(shouldRetry).toBe(true); verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (Request timed out)')).once(); }); - it('should send events to updated host on settings update', async () => { + it('should send events to the correct host using correct api key', async () => { when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ abort: () => {}, responsePromise: Promise.reject(new Error('Request timed out')), @@ -129,24 +128,12 @@ describe('NodeOdpEventApiManager', () => { const manager = managerInstance(); - await manager.sendEvents(ODP_EVENTS); + await manager.sendEvents(odpConfig, ODP_EVENTS); - const updatedOdpConfig = new OdpConfig( - 'updated-key', - 'https://updatedhost.test', - 'https://updatedpixel.test', - ['updated-seg'], - ) + verify(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).once(); - manager.updateSettings(updatedOdpConfig); - await manager.sendEvents(ODP_EVENTS); - - verify(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).twice(); - - const [initUrl] = capture(mockRequestHandler.makeRequest).first(); + const [initUrl, headers] = capture(mockRequestHandler.makeRequest).first(); expect(initUrl).toEqual(`${API_HOST}/v3/events`); - - const [finalUrl] = capture(mockRequestHandler.makeRequest).last(); - expect(finalUrl).toEqual(`${updatedOdpConfig.apiHost}/v3/events`); + expect(headers['x-api-key']).toEqual(odpConfig.apiKey); }); }); diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index 59a3d7669..03e026bc3 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE } from '../lib/utils/enums'; +import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE, ERROR_MESSAGES } from '../lib/utils/enums'; import { OdpConfig } from '../lib/core/odp/odp_config'; -import { STATE } from '../lib/core/odp/odp_event_manager'; +import { Status } from '../lib/core/odp/odp_event_manager'; import { BrowserOdpEventManager } from "../lib/plugins/odp/event_manager/index.browser"; import { NodeOdpEventManager, NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; @@ -152,12 +152,12 @@ describe('OdpEventManager', () => { }); beforeEach(() => { + jest.useFakeTimers(); resetCalls(mockLogger); resetCalls(mockApiManager); }); it('should update api manager setting with odp config on instantiation', () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); when(mockApiManager.updateSettings(anything())).thenReturn(undefined); const apiManager = instance(mockApiManager); @@ -170,12 +170,14 @@ describe('OdpEventManager', () => { clientVersion, }); + console.log(capture(mockApiManager.updateSettings)); + + verify(mockApiManager.updateSettings(anything())).once(); const [passedConfig] = capture(mockApiManager.updateSettings).last(); - expect(passedConfig).toEqual(odpConfig); + expect(passedConfig.equals(odpConfig)).toBeTruthy(); }); it('should update api manager setting with updatetd odp config on updateSettings', () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); when(mockApiManager.updateSettings(anything())).thenReturn(undefined); const apiManager = instance(mockApiManager); @@ -206,7 +208,7 @@ describe('OdpEventManager', () => { expect(finalConfig).toEqual(updatedOdpConfig); }); - it('should log and discard events when event manager not running', () => { + it.only('should log and discard events when event manager not running', () => { const eventManager = new OdpEventManager({ odpConfig, apiManager, @@ -222,10 +224,21 @@ describe('OdpEventManager', () => { verify(mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.')).once(); }); - it('should log and discard events when event manager config is not ready', () => { - const mockOdpConfig = mock(); - when(mockOdpConfig.isReady()).thenReturn(false); - const odpConfig = instance(mockOdpConfig); + it('should log an error and not start if start() is called without a config', () => { + const eventManager = new OdpEventManager({ + odpConfig: undefined, + apiManager, + logger, + clientEngine, + clientVersion, + }); + + eventManager.start(); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).once(); + expect(eventManager.status).toEqual(Status.Stopped); + }); + + it.only('should start() correctly after odpConfig is provided', () => { const eventManager = new OdpEventManager({ odpConfig, apiManager, @@ -233,15 +246,29 @@ describe('OdpEventManager', () => { clientEngine, clientVersion, }); - eventManager['state'] = STATE.RUNNING; // simulate running without calling start() - eventManager.sendEvent(EVENTS[0]); + expect(eventManager.status).toEqual(Status.Stopped); + eventManager.updateSettings(odpConfig); + eventManager.start(); + expect(eventManager.status).toEqual(Status.Running); + }); - // In a Node context, the events should be discarded - verify(mockLogger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.')).once(); + it.only('should log and discard events when event manager is not running', () => { + const eventManager = new OdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + }); + + expect(eventManager.status).toEqual(Status.Stopped); + eventManager.sendEvent(EVENTS[0]); + verify(mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.')).once(); + expect(eventManager.getQueue.length).toEqual(0); }); - it('should discard events with invalid data', () => { + it.only('should discard events with invalid data', () => { const eventManager = new OdpEventManager({ odpConfig, apiManager, @@ -262,6 +289,7 @@ describe('OdpEventManager', () => { eventManager.sendEvent(badEvent); verify(mockLogger.log(LogLevel.ERROR, 'Event data found to be invalid.')).once(); + expect(eventManager.getQueue.length).toEqual(0); }); it('should log a max queue hit and discard ', () => { @@ -274,9 +302,10 @@ describe('OdpEventManager', () => { clientVersion, queueSize: 1, // With max queue size set to 1... }); - eventManager['state'] = STATE.RUNNING; - eventManager['queue'].push(EVENTS[0]); // simulate 1 event already in the queue then... + eventManager.start(); + + eventManager['queue'].push(EVENTS[0]); // simulate 1 event already in the queue then... // ...try adding the second event eventManager.sendEvent(EVENTS[1]); @@ -533,29 +562,6 @@ describe('OdpEventManager', () => { expect(event.data.get("data_source_version") as string).not.toBeNull(); }); - it('should apply updated ODP configuration when available', () => { - const eventManager = new OdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - const apiKey = 'testing-api-key'; - const apiHost = 'https://some.other.example.com'; - const pixelUrl = 'https://some.other.pixel.com'; - const segmentsToCheck = ['empty-cart', '1-item-cart']; - const differentOdpConfig = new OdpConfig(apiKey, apiHost, pixelUrl, segmentsToCheck); - - eventManager.updateSettings(differentOdpConfig); - - expect(eventManager['odpConfig'].apiKey).toEqual(apiKey); - expect(eventManager['odpConfig'].apiHost).toEqual(apiHost); - expect(eventManager['odpConfig'].pixelUrl).toEqual(pixelUrl); - expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[0]); - expect(eventManager['odpConfig'].segmentsToCheck).toContain(Array.from(segmentsToCheck)[1]); - }); - it('should error when no identifiers are provided in Node', () => { const eventManager = new NodeOdpEventManager({ odpConfig, From 795200e1992ae94af026fa3ce4b69230ad672f13 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 29 Mar 2024 16:51:51 +0600 Subject: [PATCH 06/24] update event man --- lib/core/odp/odp_event_manager.ts | 10 +++++++--- tests/odpEventManager.spec.ts | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index 6799c8426..b7308b021 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -204,7 +204,6 @@ export abstract class OdpEventManager implements IOdpEventManager { this.flush(false); this.odpConfig = odpConfig; - this.apiManager.updateSettings(odpConfig); } /** @@ -381,6 +380,11 @@ export abstract class OdpEventManager implements IOdpEventManager { * @private */ private makeAndSend1Batch({ retry = true } : { retry: boolean }): void { + if (!this.odpConfig) { + return; + } + + const odpConfig = this.odpConfig; const batch = new Array(); // remove a batch from the queue @@ -395,7 +399,7 @@ export abstract class OdpEventManager implements IOdpEventManager { if (batch.length > 0) { if (!retry) { - this.apiManager.sendEvents(batch); + this.apiManager.sendEvents(odpConfig, batch); return; } @@ -404,7 +408,7 @@ export abstract class OdpEventManager implements IOdpEventManager { let shouldRetry: boolean; let attemptNumber = 0; do { - shouldRetry = await this.apiManager.sendEvents(batch); + shouldRetry = await this.apiManager.sendEvents(odpConfig, batch); attemptNumber += 1; } while (shouldRetry && attemptNumber < MAX_RETRIES); }); diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index 03e026bc3..57592b030 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -292,6 +292,28 @@ describe('OdpEventManager', () => { expect(eventManager.getQueue.length).toEqual(0); }); + it('should log a max queue hit and discard ', () => { + // set queue to maximum of 1 + const eventManager = new OdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + queueSize: 1, // With max queue size set to 1... + }); + + eventManager.start(); + + eventManager['queue'].push(EVENTS[0]); // simulate 1 event already in the queue then... + // ...try adding the second event + eventManager.sendEvent(EVENTS[1]); + + verify( + mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', 1) + ).once(); + }); + it('should log a max queue hit and discard ', () => { // set queue to maximum of 1 const eventManager = new OdpEventManager({ From 072125acefac131574f36383b21ddfbb5b55385c Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 29 Mar 2024 19:27:29 +0600 Subject: [PATCH 07/24] add sement manager test --- lib/core/odp/odp_event_manager.ts | 56 +++++++++---------------------- tests/odpSegmentManager.spec.ts | 13 +++++++ 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index b7308b021..81e60ae00 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -201,16 +201,15 @@ export abstract class OdpEventManager implements IOdpEventManager { return; } - this.flush(false); - + this.flush(); this.odpConfig = odpConfig; } /** * Cleans up all pending events; */ - flush(retry = true): void { - this.processQueue({ shouldFlush: true, retry }); + flush(): void { + this.processQueue(true); } /** @@ -232,8 +231,8 @@ export abstract class OdpEventManager implements IOdpEventManager { async stop(): Promise { this.logger.log(LogLevel.DEBUG, 'Stop requested.'); - await this.processQueue({ shouldFlush: true }); - + this.flush(); + this.clearCurrentTimeout(); this.status = Status.Stopped; this.logger.log(LogLevel.DEBUG, 'Stopped. Queue Count: %s', this.queue.length); } @@ -324,34 +323,25 @@ export abstract class OdpEventManager implements IOdpEventManager { * @param shouldFlush Flush all events regardless of available queue event count * @private */ - private processQueue(options?: { - shouldFlush?: boolean; - retry?: boolean; - }): void { - const { shouldFlush = false, retry = true } = options || {}; - + private processQueue(shouldFlush = true): void { if (this.status !== Status.Running) { return; } + this.clearCurrentTimeout(); + if (shouldFlush) { // clear the queue completely - this.clearCurrentTimeout(); - while (this.queueContainsItems()) { - this.makeAndSend1Batch({ retry }); + this.makeAndSend1Batch(); } - } - // Check if queue has a full batch available - else if (this.queueHasBatches()) { - this.clearCurrentTimeout(); - + } else if (this.queueHasBatches()) { + // Check if queue has a full batch available while (this.queueHasBatches()) { - this.makeAndSend1Batch({ retry }); + this.makeAndSend1Batch(); } } - this.status = Status.Running; this.setNewTimeout(); } @@ -372,37 +362,23 @@ export abstract class OdpEventManager implements IOdpEventManager { if (this.timeoutId !== undefined) { return; } - this.timeoutId = setTimeout(() => this.processQueue({ shouldFlush: true }), this.flushInterval); + this.timeoutId = setTimeout(() => this.processQueue(true), this.flushInterval); } /** * Make a batch and send it to ODP * @private */ - private makeAndSend1Batch({ retry = true } : { retry: boolean }): void { + private makeAndSend1Batch(): void { if (!this.odpConfig) { return; } - const odpConfig = this.odpConfig; - const batch = new Array(); + const batch = this.queue.splice(0, this.batchSize); - // remove a batch from the queue - for (let count = 0; count < this.batchSize; count += 1) { - const event = this.queue.shift(); - if (event) { - batch.push(event); - } else { - break; - } - } + const odpConfig = this.odpConfig; if (batch.length > 0) { - if (!retry) { - this.apiManager.sendEvents(odpConfig, batch); - return; - } - // put sending the event on another event loop setImmediate(async () => { let shouldRetry: boolean; diff --git a/tests/odpSegmentManager.spec.ts b/tests/odpSegmentManager.spec.ts index b43f1b5d6..99d3c2f62 100644 --- a/tests/odpSegmentManager.spec.ts +++ b/tests/odpSegmentManager.spec.ts @@ -128,6 +128,19 @@ describe('OdpSegmentManager', () => { expect(cacheCount(manager)).toBe(1); }); + it('should reset the cache on settings update.', async () => { + const oldConfig = new OdpConfig('old-key', 'old-host', 'pixel-url', ['new-customer']); + const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); + + setCache(manager, userKey, userValue, ['a']); + expect(cacheCount(manager)).toBe(1); + + const newConfig = new OdpConfig('new-key', 'new-host', 'pixel-url', ['new-customer']); + manager.updateSettings(newConfig); + + expect(cacheCount(manager)).toBe(0); + }); + it('should reset the cache if the option string is included in the options array.', async () => { const manager = new OdpSegmentManager(getSegmentsCache(), apiManager, mockLogHandler, validTestOdpConfig); setCache(manager, userKey, userValue, ['a']); From acc61ee615bae9ab043d2f04b0d0baed2e6bbbfc Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 29 Mar 2024 23:45:03 +0600 Subject: [PATCH 08/24] fix odp event manager tests --- lib/core/odp/odp_event_manager.ts | 13 +- tests/odpEventManager.spec.ts | 322 ++++++++++++++++++------------ 2 files changed, 203 insertions(+), 132 deletions(-) diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index 81e60ae00..8c277a692 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -129,6 +129,8 @@ export abstract class OdpEventManager implements IOdpEventManager { */ private readonly userAgentParser?: IUserAgentParser; + private retires: number; + /** * Information about the user agent @@ -146,6 +148,7 @@ export abstract class OdpEventManager implements IOdpEventManager { batchSize, flushInterval, userAgentParser, + retries, }: { odpConfig?: OdpConfig; apiManager: IOdpEventApiManager; @@ -156,6 +159,7 @@ export abstract class OdpEventManager implements IOdpEventManager { batchSize?: number; flushInterval?: number; userAgentParser?: IUserAgentParser; + retries?: number; }) { this.apiManager = apiManager; this.logger = logger; @@ -164,6 +168,7 @@ export abstract class OdpEventManager implements IOdpEventManager { this.initParams(batchSize, queueSize, flushInterval); this.status = Status.Stopped; this.userAgentParser = userAgentParser; + this.retires = retries || MAX_RETRIES; if (userAgentParser) { const { os, device } = userAgentParser.parseUserAgentInfo(); @@ -323,7 +328,7 @@ export abstract class OdpEventManager implements IOdpEventManager { * @param shouldFlush Flush all events regardless of available queue event count * @private */ - private processQueue(shouldFlush = true): void { + private processQueue(shouldFlush = false): void { if (this.status !== Status.Running) { return; } @@ -375,18 +380,18 @@ export abstract class OdpEventManager implements IOdpEventManager { } const batch = this.queue.splice(0, this.batchSize); - + const odpConfig = this.odpConfig; if (batch.length > 0) { // put sending the event on another event loop - setImmediate(async () => { + queueMicrotask(async () => { let shouldRetry: boolean; let attemptNumber = 0; do { shouldRetry = await this.apiManager.sendEvents(odpConfig, batch); attemptNumber += 1; - } while (shouldRetry && attemptNumber < MAX_RETRIES); + } while (shouldRetry && attemptNumber < this.retires); }); } } diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index 57592b030..dc71119e3 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -25,6 +25,7 @@ import { LogHandler, LogLevel } from '../lib/modules/logging'; import { OdpEvent } from '../lib/core/odp/odp_event'; import { IUserAgentParser } from '../lib/core/odp/user_agent_parser'; import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; +import exp from 'constants'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com'; @@ -157,73 +158,6 @@ describe('OdpEventManager', () => { resetCalls(mockApiManager); }); - it('should update api manager setting with odp config on instantiation', () => { - when(mockApiManager.updateSettings(anything())).thenReturn(undefined); - - const apiManager = instance(mockApiManager); - - const eventManager = new OdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - - console.log(capture(mockApiManager.updateSettings)); - - verify(mockApiManager.updateSettings(anything())).once(); - const [passedConfig] = capture(mockApiManager.updateSettings).last(); - expect(passedConfig.equals(odpConfig)).toBeTruthy(); - }); - - it('should update api manager setting with updatetd odp config on updateSettings', () => { - when(mockApiManager.updateSettings(anything())).thenReturn(undefined); - - const apiManager = instance(mockApiManager); - - const eventManager = new OdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - - const updatedOdpConfig = new OdpConfig( - 'updated-key', - 'https://updatedhost.test', - 'https://pixel.test', - ['updated-seg'], - ) - - eventManager.updateSettings(updatedOdpConfig); - - verify(mockApiManager.updateSettings(anything())).twice(); - - const [initConfig] = capture(mockApiManager.updateSettings).first(); - expect(initConfig).toEqual(odpConfig); - - const [finalConfig] = capture(mockApiManager.updateSettings).last(); - expect(finalConfig).toEqual(updatedOdpConfig); - }); - - it.only('should log and discard events when event manager not running', () => { - const eventManager = new OdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - }); - // since we've not called start() then... - - eventManager.sendEvent(EVENTS[0]); - - // ...we should get a notice after trying to send an event - verify(mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.')).once(); - }); - it('should log an error and not start if start() is called without a config', () => { const eventManager = new OdpEventManager({ odpConfig: undefined, @@ -238,7 +172,7 @@ describe('OdpEventManager', () => { expect(eventManager.status).toEqual(Status.Stopped); }); - it.only('should start() correctly after odpConfig is provided', () => { + it('should start() correctly after odpConfig is provided', () => { const eventManager = new OdpEventManager({ odpConfig, apiManager, @@ -253,7 +187,7 @@ describe('OdpEventManager', () => { expect(eventManager.status).toEqual(Status.Running); }); - it.only('should log and discard events when event manager is not running', () => { + it('should log and discard events when event manager is not running', () => { const eventManager = new OdpEventManager({ odpConfig, apiManager, @@ -265,10 +199,10 @@ describe('OdpEventManager', () => { expect(eventManager.status).toEqual(Status.Stopped); eventManager.sendEvent(EVENTS[0]); verify(mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.')).once(); - expect(eventManager.getQueue.length).toEqual(0); + expect(eventManager.getQueue().length).toEqual(0); }); - it.only('should discard events with invalid data', () => { + it('should discard events with invalid data', () => { const eventManager = new OdpEventManager({ odpConfig, apiManager, @@ -276,6 +210,10 @@ describe('OdpEventManager', () => { clientEngine, clientVersion, }); + eventManager.start(); + + expect(eventManager.status).toEqual(Status.Running); + // make an event with invalid data key-value entry const badEvent = new OdpEvent( 't3', @@ -289,31 +227,9 @@ describe('OdpEventManager', () => { eventManager.sendEvent(badEvent); verify(mockLogger.log(LogLevel.ERROR, 'Event data found to be invalid.')).once(); - expect(eventManager.getQueue.length).toEqual(0); + expect(eventManager.getQueue().length).toEqual(0); }); - it('should log a max queue hit and discard ', () => { - // set queue to maximum of 1 - const eventManager = new OdpEventManager({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - queueSize: 1, // With max queue size set to 1... - }); - - eventManager.start(); - - eventManager['queue'].push(EVENTS[0]); // simulate 1 event already in the queue then... - // ...try adding the second event - eventManager.sendEvent(EVENTS[1]); - - verify( - mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', 1) - ).once(); - }); - it('should log a max queue hit and discard ', () => { // set queue to maximum of 1 const eventManager = new OdpEventManager({ @@ -344,6 +260,8 @@ describe('OdpEventManager', () => { clientEngine, clientVersion, }); + eventManager.start(); + const processedEventData = PROCESSED_EVENTS[0].data; const eventData = eventManager['augmentCommonData'](EVENTS[0].data); @@ -369,17 +287,19 @@ describe('OdpEventManager', () => { clientVersion, flushInterval: 100, }); + const spiedEventManager = spy(eventManager); eventManager.start(); // do not add events to the queue, but allow for... - await pause(400); // at least 3 flush intervals executions (giving a little longer) + jest.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) verify(spiedEventManager['processQueue'](anything())).atLeast(3); }); - it('should dispatch events in correct number of batches', async () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); + it('should dispatch events in correct batch sizes', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); + const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ odpConfig, @@ -392,14 +312,20 @@ describe('OdpEventManager', () => { }); eventManager.start(); + for (let i = 0; i < 25; i += 1) { eventManager.sendEvent(makeEvent(i)); } - await pause(1500); + jest.runAllTicks(); + // as we are not advancing the jest fake timers, no flush should occur // ...there should be 3 batches: // batch #1 with 10, batch #2 with 10, and batch #3 (after flushInterval lapsed) with 5 = 25 events - verify(mockApiManager.sendEvents(anything())).thrice(); + verify(mockApiManager.sendEvents(anything(), anything())).twice(); + + // rest of the events should now be flushed + jest.advanceTimersByTime(250); + verify(mockApiManager.sendEvents(anything(), anything())).thrice(); }); it('should dispatch events with correct payload', async () => { @@ -415,11 +341,11 @@ describe('OdpEventManager', () => { eventManager.start(); EVENTS.forEach(event => eventManager.sendEvent(event)); - await pause(1000); + jest.advanceTimersByTime(100); // sending 1 batch of 2 events after flushInterval since batchSize is 10 - verify(mockApiManager.sendEvents(anything())).once(); - const [events] = capture(mockApiManager.sendEvents).last(); + verify(mockApiManager.sendEvents(anything(), anything())).once(); + const [_, events] = capture(mockApiManager.sendEvents).last(); expect(events.length).toEqual(2); expect(events[0].identifiers.size).toEqual(PROCESSED_EVENTS[0].identifiers.size); expect(events[0].data.size).toEqual(PROCESSED_EVENTS[0].data.size); @@ -427,6 +353,28 @@ describe('OdpEventManager', () => { expect(events[1].data.size).toEqual(PROCESSED_EVENTS[1].data.size); }); + it('should dispatch events with correct odpConfig', async () => { + const eventManager = new OdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 10, + flushInterval: 100, + }); + + eventManager.start(); + EVENTS.forEach(event => eventManager.sendEvent(event)); + + jest.advanceTimersByTime(100); + + // sending 1 batch of 2 events after flushInterval since batchSize is 10 + verify(mockApiManager.sendEvents(anything(), anything())).once(); + const [usedOdpConfig] = capture(mockApiManager.sendEvents).last(); + expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); + }); + it('should augment events with data from user agent parser', async () => { const userAgentParser : IUserAgentParser = { parseUserAgentInfo: function (): UserAgentInfo { @@ -450,10 +398,10 @@ describe('OdpEventManager', () => { eventManager.start(); EVENTS.forEach(event => eventManager.sendEvent(event)); - await pause(1000); + jest.advanceTimersByTime(100); - verify(mockApiManager.sendEvents(anything())).called(); - const [events] = capture(mockApiManager.sendEvents).last(); + verify(mockApiManager.sendEvents(anything(), anything())).called(); + const [_, events] = capture(mockApiManager.sendEvents).last(); const event = events[0]; expect(event.data.get('os')).toEqual('windows'); @@ -463,8 +411,9 @@ describe('OdpEventManager', () => { }); it('should retry failed events', async () => { - // all events should fail ie shouldRetry = true - when(mockApiManager.sendEvents(anything())).thenResolve(true); + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(true) + + const retries = 3; const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ odpConfig, @@ -472,23 +421,55 @@ describe('OdpEventManager', () => { logger, clientEngine, clientVersion, - batchSize: 2, // batch size of 2 + batchSize: 2, flushInterval: 100, + retries, }); eventManager.start(); - // send 4 events for (let i = 0; i < 4; i += 1) { eventManager.sendEvent(makeEvent(i)); } - await pause(1500); - // retry 3x (default) for 2 batches or 6 calls to attempt to process - verify(mockApiManager.sendEvents(anything())).times(6); + jest.runAllTicks(); + jest.useRealTimers(); + await pause(100); + + // retry 3x for 2 batches or 6 calls to attempt to process + verify(mockApiManager.sendEvents(anything(), anything())).times(6); + }); + + it('should flush all queued events when flush() is called', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); + + const apiManager = instance(mockApiManager); + const eventManager = new OdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 200, + flushInterval: 100, + }); + + eventManager.start(); + for (let i = 0; i < 25; i += 1) { + eventManager.sendEvent(makeEvent(i)); + } + + expect(eventManager.getQueue().length).toEqual(25); + + eventManager.flush(); + + jest.runAllTicks(); + + verify(mockApiManager.sendEvents(anything(), anything())).once(); + expect(eventManager.getQueue().length).toEqual(0); }); - it('should flush all scheduled events before stopping', async () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); + it('should flush all queued events before stopping', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); const eventManager = new OdpEventManager({ odpConfig, @@ -496,25 +477,105 @@ describe('OdpEventManager', () => { logger, clientEngine, clientVersion, - batchSize: 2, // batches of 2 with... + batchSize: 200, flushInterval: 100, }); eventManager.start(); - // ...25 events should... for (let i = 0; i < 25; i += 1) { eventManager.sendEvent(makeEvent(i)); } - await pause(300); - await eventManager.stop(); - verify(mockLogger.log(LogLevel.DEBUG, 'Stop requested.')).once(); - verify(mockLogger.log(LogLevel.DEBUG, 'Stopped. Queue Count: %s', 0)).once(); + expect(eventManager.getQueue().length).toEqual(25); + + eventManager.flush(); + + jest.runAllTicks(); + + verify(mockApiManager.sendEvents(anything(), anything())).once(); + expect(eventManager.getQueue().length).toEqual(0); + }); + + it('should flush all queued events using the old odpConfig when updateSettings is called()', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); + + const odpConfig = new OdpConfig('old-key', 'old-host', 'https://new-odp.pixel.com', []); + const updatedConfig = new OdpConfig('new-key', 'new-host', 'https://new-odp.pixel.com', []); + + const apiManager = instance(mockApiManager); + const eventManager = new OdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 200, + flushInterval: 100, + }); + + eventManager.start(); + for (let i = 0; i < 25; i += 1) { + eventManager.sendEvent(makeEvent(i)); + } + + expect(eventManager.getQueue().length).toEqual(25); + + eventManager.updateSettings(updatedConfig); + + jest.runAllTicks(); + + verify(mockApiManager.sendEvents(anything(), anything())).once(); + expect(eventManager.getQueue().length).toEqual(0); + const [usedOdpConfig] = capture(mockApiManager.sendEvents).last(); + expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); + }); + + it('should use updated odpConfig to send events', async () => { + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); + + const odpConfig = new OdpConfig('old-key', 'old-host', 'https://new-odp.pixel.com', []); + const updatedConfig = new OdpConfig('new-key', 'new-host', 'https://new-odp.pixel.com', []); + + const apiManager = instance(mockApiManager); + const eventManager = new OdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 200, + flushInterval: 100, + }); + + eventManager.start(); + for (let i = 0; i < 25; i += 1) { + eventManager.sendEvent(makeEvent(i)); + } + + expect(eventManager.getQueue().length).toEqual(25); + + jest.advanceTimersByTime(100); + + expect(eventManager.getQueue().length).toEqual(0); + let [usedOdpConfig] = capture(mockApiManager.sendEvents).first(); + expect(usedOdpConfig.equals(odpConfig)).toBeTruthy(); + + eventManager.updateSettings(updatedConfig); + jest.runAllTicks(); + + + for (let i = 0; i < 25; i += 1) { + eventManager.sendEvent(makeEvent(i)); + } + jest.advanceTimersByTime(100); + + expect(eventManager.getQueue().length).toEqual(0); + ([usedOdpConfig] = capture(mockApiManager.sendEvents).last()); + expect(usedOdpConfig.equals(updatedConfig)).toBeTruthy(); }); it('should prepare correct payload for register VUID', async () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); - when(mockApiManager.updateSettings(anything())).thenReturn(undefined); + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); @@ -533,9 +594,10 @@ describe('OdpEventManager', () => { eventManager.start(); eventManager.registerVuid(vuid); - await pause(1500); - const [events] = capture(mockApiManager.sendEvents).last(); + jest.advanceTimersByTime(250); + + const [_, events] = capture(mockApiManager.sendEvents).last(); expect(events.length).toBe(1); const [event] = events; @@ -549,8 +611,7 @@ describe('OdpEventManager', () => { }); it('should send correct event payload for identify user', async () => { - when(mockApiManager.sendEvents(anything())).thenResolve(false); - when(mockApiManager.updateSettings(anything())).thenReturn(undefined); + when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); @@ -569,9 +630,10 @@ describe('OdpEventManager', () => { eventManager.start(); eventManager.identifyUser(fsUserId, vuid); - await pause(1500); - const [events] = capture(mockApiManager.sendEvents).last(); + jest.advanceTimersByTime(250); + + const [_, events] = capture(mockApiManager.sendEvents).last(); expect(events.length).toBe(1); const [event] = events; @@ -598,6 +660,8 @@ describe('OdpEventManager', () => { eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); eventManager.stop(); + jest.runAllTicks(); + verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).twice(); }); @@ -615,6 +679,8 @@ describe('OdpEventManager', () => { eventManager.sendEvent(EVENT_WITH_UNDEFINED_IDENTIFIER); eventManager.stop(); + jest.runAllTicks(); + verify(mockLogger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.')).never(); }); }); From 34356720d0e0a2f3494ab81030e1564958955cbd Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Sat, 30 Mar 2024 02:55:26 +0600 Subject: [PATCH 09/24] odp manager tests --- lib/core/odp/odp_manager.ts | 20 ++- tests/odpManager.spec.ts | 322 +++++++++++++++++++++--------------- 2 files changed, 202 insertions(+), 140 deletions(-) diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 4b9db3b12..47d805288 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -52,7 +52,7 @@ export interface IOdpManager { getVuid(): string | undefined; } -enum Status { +export enum Status { Running, Stopped, } @@ -73,7 +73,7 @@ export abstract class OdpManager implements IOdpManager { */ private configPromise: ResolvablePromise; - private status: Status = Status.Stopped; + status: Status = Status.Stopped; /** * ODP Segment Manager which provides an interface to the remote ODP server (GraphQL API) for audience segments mapping. @@ -81,7 +81,17 @@ export abstract class OdpManager implements IOdpManager { */ private segmentManager: IOdpSegmentManager; - /** + /**constructor({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + }: { + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; + eventManager: IOdpEventManager; + logger: LogHandler; + }) * ODP Event Manager which provides an interface to the remote ODP server (REST API) for events. * It will queue all pending events (persistent) and send them (in batches of up to 10 events) to the ODP server when possible. */ @@ -136,6 +146,10 @@ export abstract class OdpManager implements IOdpManager { } } + public getStatus(): Status { + return this.status; + } + async start(): Promise { if (this.status === Status.Running) { return; diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index 5d3b0e465..947a076cb 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -24,13 +24,13 @@ import { LogHandler, LogLevel } from '../lib/modules/logging'; import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; -import { NodeOdpManager as OdpManager } from './../lib/plugins/odp_manager/index.node'; -import { OdpConfig } from '../lib/core/odp/odp_config'; +import { OdpManager, Status } from '../lib/core/odp/odp_manager'; +import { OdpConfig, OdpIntegrationConfig } from '../lib/core/odp/odp_config'; import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; import { NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; -import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; +import { IOdpSegmentManager, OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; -import { ServerLRUCache } from '../lib/utils/lru_cache'; +import { IOdpEventManager } from '../lib/shared_types'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -44,6 +44,42 @@ const pixelB = 'pixel-b'; const segmentsB = ['b']; const userB = 'fs-user-b'; +class TestOdpManager extends OdpManager { + vuidEnabled: boolean; + vuid: string; + vuidInitializer: () => Promise; + + constructor({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled, + vuid, + }: { + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; + eventManager: IOdpEventManager; + logger: LogHandler; + vuidEnabled?: boolean; + vuid?: string; + vuidInitializer?: () => Promise; + }) { + super({ odpIntegrationConfig, segmentManager, eventManager, logger }); + this.vuidEnabled = vuidEnabled ?? false; + this.vuid = vuid ?? 'vuid_123'; + } + isVuidEnabled(): boolean { + return this.vuidEnabled; + } + getVuid(): string { + return this.vuid; + } + protected initializeVuid(): Promise { + return + } +} + describe('OdpManager', () => { let mockLogger: LogHandler; let mockRequestHandler: RequestHandler; @@ -66,7 +102,6 @@ describe('OdpManager', () => { mockLogger = mock(); mockRequestHandler = mock(); - odpConfig = new OdpConfig(); logger = instance(mockLogger); defaultRequestHandler = instance(mockRequestHandler); @@ -89,142 +124,155 @@ describe('OdpManager', () => { resetCalls(mockSegmentManager); }); - const odpManagerInstance = (config?: OdpConfig) => - new OdpManager({ - odpOptions: { - eventManager, - segmentManager, - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, - }); - - it('should drop relevant calls when OdpManager is initialized with the disabled flag', async () => { - const odpManager = new OdpManager({ + // const odpManagerInstance = (config?: OdpConfig) => + // new OdpManager({ + // odpOptions: { + // eventManager, + // segmentManager, + // segmentsRequestHandler: defaultRequestHandler, + // eventRequestHandler: defaultRequestHandler, + // }, + // }); + + // it('should drop relevant calls when OdpManager is initialized with the disabled flag', async () => { + // const odpManager = new OdpManager({ + // logger, + // odpOptions: { + // disabled: true, + // segmentsRequestHandler: defaultRequestHandler, + // eventRequestHandler: defaultRequestHandler, + // }, + // }); + // verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); + + // odpManager.updateSettings(new OdpConfig('valid', 'host', 'pixel-url', [])); + // expect(odpManager.odpConfig).toBeUndefined; + + // await odpManager.fetchQualifiedSegments('user1', []); + // verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); + + // odpManager.identifyUser('user1'); + // verify(mockLogger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED)).once(); + + // expect(odpManager.eventManager).toBeUndefined; + // expect(odpManager.segmentManager).toBeUndefined; + // }); + + + it('should be in stopped status and not ready if constructed without odpIntegrationConfig', () => { + const odpManager = new TestOdpManager({ + segmentManager, + eventManager, logger, - odpOptions: { - disabled: true, - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, - }); - verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); - - odpManager.updateSettings(new OdpConfig('valid', 'host', 'pixel-url', [])); - expect(odpManager.odpConfig).toBeUndefined; - - await odpManager.fetchQualifiedSegments('user1', []); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); - - odpManager.identifyUser('user1'); - verify(mockLogger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED)).once(); - - expect(odpManager.eventManager).toBeUndefined; - expect(odpManager.segmentManager).toBeUndefined; - }); - - it('should start ODP Event Manager when ODP Manager is initialized', () => { - const odpManager = odpManagerInstance(); - verify(mockEventManager.start()).once(); - expect(odpManager.eventManager).not.toBeUndefined(); - }); - - it('should stop ODP Event Manager when close is called', () => { - const odpManager = odpManagerInstance(); - verify(mockEventManager.stop()).never(); - - odpManager.close(); - verify(mockEventManager.stop()).once(); - }); - - it('should use new settings in event manager when ODP Config is updated', async () => { - const odpManager = new OdpManager({ - odpOptions: { - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - eventManager: new OdpEventManager({ - odpConfig, - apiManager: eventApiManager, - logger, - clientEngine: '', - clientVersion: '', - batchSize: 1, - flushInterval: 250, - }), - }, - }); - - odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); - - expect(odpManager.odpConfig.apiKey).toBe(keyA); - expect(odpManager.odpConfig.apiHost).toBe(hostA); - expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); - - // odpManager.identifyUser(userA); - - // verify(mockEventApiManager.sendEvents(keyA, hostA, anything())).once(); - - odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); - expect(odpManager.odpConfig.apiKey).toBe(keyB); - expect(odpManager.odpConfig.apiHost).toBe(hostB); - expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); - - // odpManager.identifyUser(userB); - - // verify(mockEventApiManager.sendEvents(keyB, hostB, anything())).once(); - }); - - it('should use new settings in segment manager when ODP Config is updated', async () => { - const odpManager = new OdpManager({ - odpOptions: { - segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), segmentApiManager), - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, }); - odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); - - expect(odpManager.odpConfig.apiKey).toBe(keyA); - expect(odpManager.odpConfig.apiHost).toBe(hostA); - expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); - - await odpManager.fetchQualifiedSegments(userA); - verify(mockSegmentApiManager.fetchSegments(keyA, hostA, ODP_USER_KEY.FS_USER_ID, userA, anything())).once(); - - odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); - expect(odpManager.odpConfig.apiKey).toBe(keyB); - expect(odpManager.odpConfig.apiHost).toBe(hostB); - expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); - - await odpManager.fetchQualifiedSegments(userB); - verify(mockSegmentApiManager.fetchSegments(keyB, hostB, ODP_USER_KEY.FS_USER_ID, userB, anything())).once(); + expect(odpManager.isReady()).toBe(false); + expect(odpManager.getStatus()).toEqual(Status.Stopped); }); - it('should get event manager', () => { - const odpManagerA = odpManagerInstance(); - expect(odpManagerA.eventManager).not.toBe(null); - const odpManagerB = new OdpManager({ - logger, - odpOptions: { - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, - }); - expect(odpManagerB.eventManager).not.toBe(null); - }); - - it('should get segment manager', () => { - const odpManagerA = odpManagerInstance(); - expect(odpManagerA.segmentManager).not.toBe(null); - - const odpManagerB = new OdpManager({ - odpOptions: { - segmentsRequestHandler: defaultRequestHandler, - eventRequestHandler: defaultRequestHandler, - }, - }); - expect(odpManagerB.eventManager).not.toBe(null); - }); + // it('should start ODP Event Manager when ODP Manager is initialized', () => { + // const odpManager = odpManagerInstance(); + // verify(mockEventManager.start()).once(); + // expect(odpManager.eventManager).not.toBeUndefined(); + // }); + + // it('should stop ODP Event Manager when close is called', () => { + // const odpManager = odpManagerInstance(); + // verify(mockEventManager.stop()).never(); + + // odpManager.close(); + // verify(mockEventManager.stop()).once(); + // }); + + // // it('should use new settings in event manager when ODP Config is updated', async () => { + // // const odpManager = new OdpManager({ + // // odpOptions: { + // // segmentsRequestHandler: defaultRequestHandler, + // // eventRequestHandler: defaultRequestHandler, + // // eventManager: new OdpEventManager({ + // // odpConfig, + // // apiManager: eventApiManager, + // // logger, + // // clientEngine: '', + // // clientVersion: '', + // // batchSize: 1, + // // flushInterval: 250, + // // }), + // // }, + // // }); + + // odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); + + // expect(odpManager.odpConfig.apiKey).toBe(keyA); + // expect(odpManager.odpConfig.apiHost).toBe(hostA); + // expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); + + // // odpManager.identifyUser(userA); + + // // verify(mockEventApiManager.sendEvents(keyA, hostA, anything())).once(); + + // odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); + // expect(odpManager.odpConfig.apiKey).toBe(keyB); + // expect(odpManager.odpConfig.apiHost).toBe(hostB); + // expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); + + // // odpManager.identifyUser(userB); + + // // verify(mockEventApiManager.sendEvents(keyB, hostB, anything())).once(); + // }); + + // it('should use new settings in segment manager when ODP Config is updated', async () => { + // const odpManager = new OdpManager({ + // odpOptions: { + // segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), segmentApiManager), + // segmentsRequestHandler: defaultRequestHandler, + // eventRequestHandler: defaultRequestHandler, + // }, + // }); + + // odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); + + // expect(odpManager.odpConfig.apiKey).toBe(keyA); + // expect(odpManager.odpConfig.apiHost).toBe(hostA); + // expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); + + // await odpManager.fetchQualifiedSegments(userA); + // verify(mockSegmentApiManager.fetchSegments(keyA, hostA, ODP_USER_KEY.FS_USER_ID, userA, anything())).once(); + + // odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); + // expect(odpManager.odpConfig.apiKey).toBe(keyB); + // expect(odpManager.odpConfig.apiHost).toBe(hostB); + // expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); + + // await odpManager.fetchQualifiedSegments(userB); + // verify(mockSegmentApiManager.fetchSegments(keyB, hostB, ODP_USER_KEY.FS_USER_ID, userB, anything())).once(); + // }); + + // it('should get event manager', () => { + // const odpManagerA = odpManagerInstance(); + // expect(odpManagerA.eventManager).not.toBe(null); + + // const odpManagerB = new OdpManager({ + // logger, + // odpOptions: { + // segmentsRequestHandler: defaultRequestHandler, + // eventRequestHandler: defaultRequestHandler, + // }, + // }); + // expect(odpManagerB.eventManager).not.toBe(null); + // }); + + // it('should get segment manager', () => { + // const odpManagerA = odpManagerInstance(); + // expect(odpManagerA.segmentManager).not.toBe(null); + + // const odpManagerB = new OdpManager({ + // odpOptions: { + // segmentsRequestHandler: defaultRequestHandler, + // eventRequestHandler: defaultRequestHandler, + // }, + // }); + // expect(odpManagerB.eventManager).not.toBe(null); + // }); }); From d9c15e7079a046c2832c1a5bfa04e7ae3cefb59b Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Sat, 30 Mar 2024 02:55:56 +0600 Subject: [PATCH 10/24] fix odp event manager tests --- tests/odpManager.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index 947a076cb..0aaa73023 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -68,6 +68,7 @@ class TestOdpManager extends OdpManager { super({ odpIntegrationConfig, segmentManager, eventManager, logger }); this.vuidEnabled = vuidEnabled ?? false; this.vuid = vuid ?? 'vuid_123'; + this.vuidInitializer = vuidInitializer ?? this.initializeVuid; } isVuidEnabled(): boolean { return this.vuidEnabled; From 4a7faa5178ebe93004798bd7da20ccccf28ef714 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Sun, 31 Mar 2024 19:57:46 +0600 Subject: [PATCH 11/24] odpManager tests --- lib/core/odp/odp_config.ts | 4 +- tests/odpManager.spec.ts | 391 +++++++++++++++++++++++++++++++------ tests/testUtils.ts | 4 + 3 files changed, 333 insertions(+), 66 deletions(-) diff --git a/lib/core/odp/odp_config.ts b/lib/core/odp/odp_config.ts index e271bf71c..5233f41f7 100644 --- a/lib/core/odp/odp_config.ts +++ b/lib/core/odp/odp_config.ts @@ -60,11 +60,11 @@ export class OdpConfig { } } -type OdpNotIntegratedConfig = { +export type OdpNotIntegratedConfig = { readonly integrated: false; } -type OdpIntegratedConfig = { +export type OdpIntegratedConfig = { readonly integrated: true; readonly odpConfig: OdpConfig; } diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index 0aaa73023..a9ec12960 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -15,7 +15,7 @@ */ /// -import { anything, instance, mock, resetCalls, verify } from 'ts-mockito'; +import { anything, capture, instance, mock, resetCalls, verify, when } from 'ts-mockito'; import { LOG_MESSAGES } from './../lib/utils/enums/index'; import { ERROR_MESSAGES, ODP_USER_KEY } from './../lib/utils/enums/index'; @@ -25,12 +25,15 @@ import { RequestHandler } from '../lib/utils/http_request_handler/http'; import { BrowserLRUCache } from './../lib/utils/lru_cache/browser_lru_cache'; import { OdpManager, Status } from '../lib/core/odp/odp_manager'; -import { OdpConfig, OdpIntegrationConfig } from '../lib/core/odp/odp_config'; +import { OdpConfig, OdpIntegratedConfig, OdpIntegrationConfig, OdpNotIntegratedConfig } from '../lib/core/odp/odp_config'; import { NodeOdpEventApiManager as OdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.node'; import { NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; import { IOdpSegmentManager, OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { IOdpEventManager } from '../lib/shared_types'; +import { wait } from './testUtils'; +import { resolvablePromise } from '../lib/utils/promise/resolvablePromise'; +import exp from 'constants'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -44,41 +47,38 @@ const pixelB = 'pixel-b'; const segmentsB = ['b']; const userB = 'fs-user-b'; -class TestOdpManager extends OdpManager { - vuidEnabled: boolean; - vuid: string; - vuidInitializer: () => Promise; - - constructor({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - vuidEnabled, - vuid, - }: { - odpIntegrationConfig?: OdpIntegrationConfig; - segmentManager: IOdpSegmentManager; - eventManager: IOdpEventManager; - logger: LogHandler; - vuidEnabled?: boolean; - vuid?: string; - vuidInitializer?: () => Promise; - }) { - super({ odpIntegrationConfig, segmentManager, eventManager, logger }); - this.vuidEnabled = vuidEnabled ?? false; - this.vuid = vuid ?? 'vuid_123'; - this.vuidInitializer = vuidInitializer ?? this.initializeVuid; - } - isVuidEnabled(): boolean { - return this.vuidEnabled; - } - getVuid(): string { - return this.vuid; - } - protected initializeVuid(): Promise { - return +const testOdpManager = ({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled, + vuid, + vuidInitializer, +}: { + odpIntegrationConfig?: OdpIntegrationConfig; + segmentManager: IOdpSegmentManager; + eventManager: IOdpEventManager; + logger: LogHandler; + vuidEnabled?: boolean; + vuid?: string; + vuidInitializer?: () => Promise; +}): OdpManager => { + class TestOdpManager extends OdpManager{ + constructor() { + super({ odpIntegrationConfig, segmentManager, eventManager, logger }); + } + isVuidEnabled(): boolean { + return vuidEnabled ?? false; + } + getVuid(): string { + return vuid ?? 'vuid_123'; + } + protected initializeVuid(): Promise { + return vuidInitializer?.() ?? Promise.resolve(); + } } + return new TestOdpManager(); } describe('OdpManager', () => { @@ -161,7 +161,7 @@ describe('OdpManager', () => { it('should be in stopped status and not ready if constructed without odpIntegrationConfig', () => { - const odpManager = new TestOdpManager({ + const odpManager = testOdpManager({ segmentManager, eventManager, logger, @@ -171,37 +171,300 @@ describe('OdpManager', () => { expect(odpManager.getStatus()).toEqual(Status.Stopped); }); + it('should call initialzeVuid on construction if vuid is enabled', () => { + const vuidInitializer = jest.fn(); - // it('should start ODP Event Manager when ODP Manager is initialized', () => { - // const odpManager = odpManagerInstance(); - // verify(mockEventManager.start()).once(); - // expect(odpManager.eventManager).not.toBeUndefined(); - // }); + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + vuidInitializer: vuidInitializer, + }); - // it('should stop ODP Event Manager when close is called', () => { - // const odpManager = odpManagerInstance(); - // verify(mockEventManager.stop()).never(); + expect(vuidInitializer).toHaveBeenCalledTimes(1); + }); - // odpManager.close(); - // verify(mockEventManager.stop()).once(); - // }); + it('should become ready only after odpIntegrationConfig is provided if vuid is not enabled', async () => { + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: false, + }); + + // should not be ready untill odpIntegrationConfig is provided + await wait(500); + expect(odpManager.isReady()).toBe(false); + + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + odpManager.updateSettings(odpIntegrationConfig); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + }); + + it('should become ready if odpIntegrationConfig is provided in constructor and then initialzeVuid', async () => { + const vuidPromise = resolvablePromise(); + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + + const vuidInitializer = () => { + return vuidPromise.promise; + } + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + vuidInitializer, + }); + + await wait(500); + expect(odpManager.isReady()).toBe(false); + + vuidPromise.resolve(); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + }); + + it('should become ready after odpIntegrationConfig is provided using updateSettings() and then initialzeVuid finishes', async () => { + const vuidPromise = resolvablePromise(); - // // it('should use new settings in event manager when ODP Config is updated', async () => { - // // const odpManager = new OdpManager({ - // // odpOptions: { - // // segmentsRequestHandler: defaultRequestHandler, - // // eventRequestHandler: defaultRequestHandler, - // // eventManager: new OdpEventManager({ - // // odpConfig, - // // apiManager: eventApiManager, - // // logger, - // // clientEngine: '', - // // clientVersion: '', - // // batchSize: 1, - // // flushInterval: 250, - // // }), - // // }, - // // }); + const vuidInitializer = () => { + return vuidPromise.promise; + } + + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + vuidInitializer, + }); + + + expect(odpManager.isReady()).toBe(false); + + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + odpManager.updateSettings(odpIntegrationConfig); + + await wait(500); + expect(odpManager.isReady()).toBe(false); + + vuidPromise.resolve(); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + }); + + it('should become ready after initialzeVuid finishes and then odpIntegrationConfig is provided using updateSettings()', async () => { + const vuidPromise = resolvablePromise(); + + const vuidInitializer = () => { + return vuidPromise.promise; + } + + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + vuidInitializer, + }); + + expect(odpManager.isReady()).toBe(false); + vuidPromise.resolve(); + + await wait(500); + expect(odpManager.isReady()).toBe(false); + + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + odpManager.updateSettings(odpIntegrationConfig); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + }); + + it('should become ready and stay in stopped state and not start eventManager if OdpNotIntegrated config is provided', async () => { + const vuidPromise = resolvablePromise(); + + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + const odpIntegrationConfig: OdpNotIntegratedConfig = { integrated: false }; + odpManager.updateSettings(odpIntegrationConfig); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + expect(odpManager.getStatus()).toEqual(Status.Stopped); + verify(mockEventManager.start()).never(); + }); + + it('should pass the integrated odp config given in constructor to eventManger and segmentManager', async () => { + when(mockEventManager.updateSettings(anything())).thenReturn(undefined); + when(mockSegmentManager.updateSettings(anything())).thenReturn(undefined); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + verify(mockEventManager.updateSettings(anything())).once(); + const [eventOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(eventOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + + verify(mockSegmentManager.updateSettings(anything())).once(); + const [segmentOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(segmentOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + }); + + it('should pass the integrated odp config given in updateSettings() to eventManger and segmentManager', async () => { + when(mockEventManager.updateSettings(anything())).thenReturn(undefined); + when(mockSegmentManager.updateSettings(anything())).thenReturn(undefined); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + odpManager.updateSettings(odpIntegrationConfig); + + verify(mockEventManager.updateSettings(anything())).once(); + const [eventOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(eventOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + + verify(mockSegmentManager.updateSettings(anything())).once(); + const [segmentOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(segmentOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + }); + + it('should start if odp is integrated and start odpEventManger', async () => { + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + odpManager.updateSettings(odpIntegrationConfig); + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + expect(odpManager.getStatus()).toEqual(Status.Running); + }); + + it('should just update config when updateSettings is called in running state', async () => { + const odpManager = testOdpManager({ + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + odpManager.updateSettings(odpIntegrationConfig); + + await odpManager.onReady(); + expect(odpManager.isReady()).toBe(true); + expect(odpManager.getStatus()).toEqual(Status.Running); + + const newOdpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyB, hostB, pixelB, segmentsB) + }; + + odpManager.updateSettings(newOdpIntegrationConfig); + + verify(mockEventManager.start()).once(); + verify(mockEventManager.stop()).never(); + verify(mockEventManager.updateSettings(anything())).twice(); + const [firstEventOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(firstEventOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + const [secondEventOdpConfig] = capture(mockEventManager.updateSettings).second(); + expect(secondEventOdpConfig.equals(newOdpIntegrationConfig.odpConfig)).toBe(true); + + verify(mockSegmentManager.updateSettings(anything())).twice(); + const [firstSegmentOdpConfig] = capture(mockEventManager.updateSettings).first(); + expect(firstSegmentOdpConfig.equals(odpIntegrationConfig.odpConfig)).toBe(true); + const [secondSegmentOdpConfig] = capture(mockEventManager.updateSettings).second(); + expect(secondSegmentOdpConfig.equals(newOdpIntegrationConfig.odpConfig)).toBe(true); + }); + + it('should stop and stop eventManager if OdpNotIntegrated config is updated in running state', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + expect(odpManager.isReady()).toBe(true); + expect(odpManager.getStatus()).toEqual(Status.Running); + + const newOdpIntegrationConfig: OdpNotIntegratedConfig = { + integrated: false, + }; + + odpManager.updateSettings(newOdpIntegrationConfig); + + expect(odpManager.getStatus()).toEqual(Status.Stopped); + verify(mockEventManager.stop()).once(); + }); + + // it('should use new settings in event manager when ODP Config is updated', async () => { + // const odpManager = new OdpManager({ + // odpOptions: { + // segmentsRequestHandler: defaultRequestHandler, + // eventRequestHandler: defaultRequestHandler, + // eventManager: new OdpEventManager({ + // odpConfig, + // apiManager: eventApiManager, + // logger, + // clientEngine: '', + // clientVersion: '', + // batchSize: 1, + // flushInterval: 250, + // }), + // }, + // }); // odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); diff --git a/tests/testUtils.ts b/tests/testUtils.ts index 2c28c259b..3145502c9 100644 --- a/tests/testUtils.ts +++ b/tests/testUtils.ts @@ -57,3 +57,7 @@ export const getTestPersistentCache = (): PersistentKeyValueCache => { return cache; } + +export const wait = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; From 8f64d30763b416e404565feb179457c2248b7381 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 2 Apr 2024 19:52:13 +0600 Subject: [PATCH 12/24] odp manager tests --- lib/core/odp/odp_config.ts | 1 - lib/core/odp/odp_manager.ts | 4 - tests/odpManager.spec.ts | 169 ++++++++++++++++++++---------------- 3 files changed, 96 insertions(+), 78 deletions(-) diff --git a/lib/core/odp/odp_config.ts b/lib/core/odp/odp_config.ts index 5233f41f7..f394a0868 100644 --- a/lib/core/odp/odp_config.ts +++ b/lib/core/odp/odp_config.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { config } from 'chai'; import { checkArrayEquality } from '../../utils/fns'; export class OdpConfig { diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 47d805288..47421da19 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -288,10 +288,6 @@ export abstract class OdpManager implements IOdpManager { throw new Error(ERROR_MESSAGES.ODP_INVALID_DATA); } - if (!this.eventManager) { - throw new Error(ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_EVENT_MANAGER_MISSING); - } - if (typeof action !== 'string' || action === '') { throw new Error('ODP action is not valid (cannot be empty).'); } diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index a9ec12960..c1bac8f08 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -449,94 +449,117 @@ describe('OdpManager', () => { verify(mockEventManager.stop()).once(); }); - // it('should use new settings in event manager when ODP Config is updated', async () => { - // const odpManager = new OdpManager({ - // odpOptions: { - // segmentsRequestHandler: defaultRequestHandler, - // eventRequestHandler: defaultRequestHandler, - // eventManager: new OdpEventManager({ - // odpConfig, - // apiManager: eventApiManager, - // logger, - // clientEngine: '', - // clientVersion: '', - // batchSize: 1, - // flushInterval: 250, - // }), - // }, - // }); + it('should call eventManager.identifyUser with correct parameters when identifyUser is called', () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; - // odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); - // expect(odpManager.odpConfig.apiKey).toBe(keyA); - // expect(odpManager.odpConfig.apiHost).toBe(hostA); - // expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); - // // odpManager.identifyUser(userA); + odpManager.updateSettings(odpIntegrationConfig); - // // verify(mockEventApiManager.sendEvents(keyA, hostA, anything())).once(); + const userId = 'user123'; + const vuid = 'vuid_123'; - // odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); - // expect(odpManager.odpConfig.apiKey).toBe(keyB); - // expect(odpManager.odpConfig.apiHost).toBe(hostB); - // expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); + odpManager.identifyUser(userId, vuid); + const [userIdArg, vuidArg] = capture(mockEventManager.identifyUser).byCallIndex(0); + expect(userIdArg).toEqual(userId); + expect(vuidArg).toEqual(vuid); - // // odpManager.identifyUser(userB); + odpManager.identifyUser(userId); + const [userIdArg2, vuidArg2] = capture(mockEventManager.identifyUser).byCallIndex(1); + expect(userIdArg2).toEqual(userId); + expect(vuidArg2).toEqual(undefined); + + odpManager.identifyUser(vuid); + const [userIdArg3, vuidArg3] = capture(mockEventManager.identifyUser).byCallIndex(2); + expect(userIdArg3).toEqual(undefined); + expect(vuidArg3).toEqual(vuid); + }); - // // verify(mockEventApiManager.sendEvents(keyB, hostB, anything())).once(); - // }); + it('should send event with correct parameters', () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; - // it('should use new settings in segment manager when ODP Config is updated', async () => { - // const odpManager = new OdpManager({ - // odpOptions: { - // segmentManager: new OdpSegmentManager(odpConfig, new BrowserLRUCache(), segmentApiManager), - // segmentsRequestHandler: defaultRequestHandler, - // eventRequestHandler: defaultRequestHandler, - // }, - // }); + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); - // odpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); + odpManager.updateSettings(odpIntegrationConfig); - // expect(odpManager.odpConfig.apiKey).toBe(keyA); - // expect(odpManager.odpConfig.apiHost).toBe(hostA); - // expect(odpManager.odpConfig.pixelUrl).toBe(pixelA); + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', 'value1'], ['key2', 'value2']]); - // await odpManager.fetchQualifiedSegments(userA); - // verify(mockSegmentApiManager.fetchSegments(keyA, hostA, ODP_USER_KEY.FS_USER_ID, userA, anything())).once(); + odpManager.sendEvent({ + action: 'action', + type: 'type', + identifiers, + data, + }); - // odpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); - // expect(odpManager.odpConfig.apiKey).toBe(keyB); - // expect(odpManager.odpConfig.apiHost).toBe(hostB); - // expect(odpManager.odpConfig.pixelUrl).toBe(pixelB); + const [event] = capture(mockEventManager.sendEvent).byCallIndex(0); + expect(event.action).toEqual('action'); + expect(event.type).toEqual('type'); + expect(event.identifiers).toEqual(identifiers); + expect(event.data).toEqual(data); + + // should use `fullstack` as type if empty string is provided + odpManager.sendEvent({ + type: '', + action: 'action', + identifiers, + data, + }); - // await odpManager.fetchQualifiedSegments(userB); - // verify(mockSegmentApiManager.fetchSegments(keyB, hostB, ODP_USER_KEY.FS_USER_ID, userB, anything())).once(); - // }); + const [event2] = capture(mockEventManager.sendEvent).byCallIndex(1); + expect(event2.action).toEqual('action'); + expect(event2.type).toEqual('fullstack'); + expect(event2.identifiers).toEqual(identifiers); + }); - // it('should get event manager', () => { - // const odpManagerA = odpManagerInstance(); - // expect(odpManagerA.eventManager).not.toBe(null); - // const odpManagerB = new OdpManager({ - // logger, - // odpOptions: { - // segmentsRequestHandler: defaultRequestHandler, - // eventRequestHandler: defaultRequestHandler, - // }, - // }); - // expect(odpManagerB.eventManager).not.toBe(null); - // }); + it('should throw an error if event action is empty string and not call eventManager', () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; - // it('should get segment manager', () => { - // const odpManagerA = odpManagerInstance(); - // expect(odpManagerA.segmentManager).not.toBe(null); + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); - // const odpManagerB = new OdpManager({ - // odpOptions: { - // segmentsRequestHandler: defaultRequestHandler, - // eventRequestHandler: defaultRequestHandler, - // }, - // }); - // expect(odpManagerB.eventManager).not.toBe(null); - // }); + odpManager.updateSettings(odpIntegrationConfig); + + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', 'value1'], ['key2', 'value2']]); + + const sendEvent = () => odpManager.sendEvent({ + action: '', + type: 'type', + identifiers, + data, + }); + + expect(sendEvent).toThrow('ODP action is not valid'); + verify(mockEventManager.sendEvent(anything())).never(); + }); }); + From ce2fe01322624b83445beff1b94c508608b8c221 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Wed, 3 Apr 2024 00:40:58 +0600 Subject: [PATCH 13/24] browserOdpManager tests fixed --- lib/core/odp/odp_manager.ts | 12 +- lib/plugins/odp_manager/index.browser.ts | 2 +- tests/odpManager.browser.spec.ts | 235 ++++------------------- tests/odpManager.spec.ts | 130 ++++++++++++- 4 files changed, 166 insertions(+), 213 deletions(-) diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 47421da19..773c8fb9f 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -81,17 +81,7 @@ export abstract class OdpManager implements IOdpManager { */ private segmentManager: IOdpSegmentManager; - /**constructor({ - odpIntegrationConfig, - segmentManager, - eventManager, - logger, - }: { - odpIntegrationConfig?: OdpIntegrationConfig; - segmentManager: IOdpSegmentManager; - eventManager: IOdpEventManager; - logger: LogHandler; - }) + /** * ODP Event Manager which provides an interface to the remote ODP server (REST API) for events. * It will queue all pending events (persistent) and send them (in batches of up to 10 events) to the ODP server when possible. */ diff --git a/lib/plugins/odp_manager/index.browser.ts b/lib/plugins/odp_manager/index.browser.ts index 3051c8f28..8cee68955 100644 --- a/lib/plugins/odp_manager/index.browser.ts +++ b/lib/plugins/odp_manager/index.browser.ts @@ -145,7 +145,7 @@ export class BrowserOdpManager extends OdpManager { /** * @override - * accesses or creates new VUID from Browser cache and registers it via the Event Manager + * accesses or creates new VUID from Browser cache */ protected async initializeVuid(): Promise { const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); diff --git a/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts index ade9d48ce..536fd7c53 100644 --- a/tests/odpManager.browser.spec.ts +++ b/tests/odpManager.browser.spec.ts @@ -27,7 +27,6 @@ import { BrowserOdpManager } from './../lib/plugins/odp_manager/index.browser'; import { IOdpEventManager, OdpOptions } from './../lib/shared_types'; import { OdpConfig } from '../lib/core/odp/odp_config'; import { BrowserOdpEventApiManager } from '../lib/plugins/odp/event_api_manager/index.browser'; -import { BrowserOdpEventManager } from '../lib/plugins/odp/event_manager/index.browser'; import { OdpSegmentManager } from './../lib/core/odp/odp_segment_manager'; import { OdpSegmentApiManager } from '../lib/core/odp/odp_segment_api_manager'; import { VuidManager } from '../lib/plugins/vuid_manager'; @@ -35,6 +34,9 @@ import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser import { IUserAgentParser } from '../lib/core/odp/user_agent_parser'; import { UserAgentInfo } from '../lib/core/odp/user_agent_info'; import { OdpEvent } from '../lib/core/odp/odp_event'; +import { LRUCache } from '../lib/utils/lru_cache'; +import { BrowserOdpEventManager } from '../lib/plugins/odp/event_manager/index.browser'; +import { OdpManager } from '../lib/core/odp/odp_manager'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -80,7 +82,7 @@ describe('OdpManager', () => { mockLogger = mock(); mockRequestHandler = mock(); - odpConfig = new OdpConfig(); + odpConfig = new OdpConfig(keyA, hostA, pixelA, segmentsA); fakeLogger = instance(mockLogger); fakeRequestHandler = instance(mockRequestHandler); @@ -106,181 +108,22 @@ describe('OdpManager', () => { }); const browserOdpManagerInstance = () => - new BrowserOdpManager({ + BrowserOdpManager.createInstance({ odpOptions: { eventManager: fakeEventManager, segmentManager: fakeSegmentManager, }, }); - it('should register VUID automatically on BrowserOdpManager initialization', async () => { + it('should create VUID automatically on BrowserOdpManager initialization', async () => { const browserOdpManager = browserOdpManagerInstance(); const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); expect(browserOdpManager.vuid).toBe(vuidManager.vuid); }); - it('should drop relevant calls when OdpManager is initialized with the disabled flag, except for VUID', async () => { - const browserOdpManager = new BrowserOdpManager({ logger: fakeLogger, odpOptions: { disabled: true } }); - - verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); - - browserOdpManager.updateSettings(new OdpConfig('valid', 'host', 'pixel-url', [])); - expect(browserOdpManager.odpConfig).toBeUndefined; - - await browserOdpManager.fetchQualifiedSegments('vuid_user1', []); - verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); - - await browserOdpManager.identifyUser('vuid_user1'); - verify(mockLogger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED)).once(); - - expect(browserOdpManager.eventManager).toBeUndefined; - expect(browserOdpManager.segmentManager).toBeUndefined; - - const vuidManager = await VuidManager.instance(BrowserOdpManager.cache); - expect(vuidManager.vuid.slice(0, 5)).toBe('vuid_'); - }); - - it('should start ODP Event Manager when ODP Manager is initialized', () => { - const browserOdpManager = browserOdpManagerInstance(); - verify(mockEventManager.start()).once(); - expect(browserOdpManager.eventManager).not.toBeUndefined(); - }); - - it('should stop ODP Event Manager when close is called', () => { - const browserOdpManager = browserOdpManagerInstance(); - verify(mockEventManager.stop()).never(); - - browserOdpManager.close(); - verify(mockEventManager.stop()).once(); - }); - - it('should use new settings in event manager when ODP Config is updated', async () => { - const browserOdpManager = new BrowserOdpManager({ - odpOptions: { - eventManager: fakeEventManager, - }, - }); - - expect(browserOdpManager.eventManager).toBeDefined(); - verify(mockEventManager.updateSettings(anything())).once(); - verify(mockEventManager.start()).once(); - - await new Promise(resolve => setTimeout(resolve, 200)); // Wait for VuidManager to fetch from cache. - - verify(mockEventManager.registerVuid(anything())).once(); - - const didUpdateA = browserOdpManager.updateSettings(odpConfigA); - expect(didUpdateA).toBe(true); - expect(browserOdpManager.odpConfig.equals(odpConfigA)).toBe(true); - - const updateSettingsArgsA = capture(mockEventManager.updateSettings).last(); - expect(updateSettingsArgsA[0]).toStrictEqual(odpConfigA); - - await browserOdpManager.identifyUser(userA); - const identifyUserArgsA = capture(mockEventManager.identifyUser).last(); - expect(identifyUserArgsA[0]).toStrictEqual(userA); - - const didUpdateB = browserOdpManager.updateSettings(odpConfigB); - expect(didUpdateB).toBe(true); - expect(browserOdpManager.odpConfig.equals(odpConfigB)).toBe(true); - - const updateSettingsArgsB = capture(mockEventManager.updateSettings).last(); - expect(updateSettingsArgsB[0]).toStrictEqual(odpConfigB); - - await browserOdpManager.eventManager!.identifyUser(userB); - const identifyUserArgsB = capture(mockEventManager.identifyUser).last(); - expect(identifyUserArgsB[0]).toStrictEqual(userB); - }); - - it('should use new settings in segment manager when ODP Config is updated', () => { - const browserOdpManager = new BrowserOdpManager({ - odpOptions: { - segmentManager: new OdpSegmentManager( - odpConfig, - new BrowserLRUCache(), - fakeSegmentApiManager - ), - }, - }); - - const didUpdateA = browserOdpManager.updateSettings(new OdpConfig(keyA, hostA, pixelA, segmentsA)); - expect(didUpdateA).toBe(true); - - browserOdpManager.fetchQualifiedSegments(vuidA); - - verify( - mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_FETCH_QUALIFIED_SEGMENTS_SEGMENTS_MANAGER_MISSING) - ).never(); - - const fetchQualifiedSegmentsArgsA = capture(mockSegmentApiManager.fetchSegments).last(); - expect(fetchQualifiedSegmentsArgsA).toStrictEqual([keyA, hostA, ODP_USER_KEY.VUID, vuidA, segmentsA]); - - const didUpdateB = browserOdpManager.updateSettings(new OdpConfig(keyB, hostB, pixelB, segmentsB)); - expect(didUpdateB).toBe(true); - - browserOdpManager.fetchQualifiedSegments(vuidB); - - const fetchQualifiedSegmentsArgsB = capture(mockSegmentApiManager.fetchSegments).last(); - expect(fetchQualifiedSegmentsArgsB).toStrictEqual([keyB, hostB, ODP_USER_KEY.VUID, vuidB, segmentsB]); - }); - - it('should get event manager', () => { - const browserOdpManagerA = browserOdpManagerInstance(); - expect(browserOdpManagerA.eventManager).not.toBe(null); - - const browserOdpManagerB = new BrowserOdpManager({}); - expect(browserOdpManagerB.eventManager).not.toBe(null); - }); - - it('should get segment manager', () => { - const browserOdpManagerA = browserOdpManagerInstance(); - expect(browserOdpManagerA.segmentManager).not.toBe(null); - - const browserOdpManagerB = new BrowserOdpManager({}); - expect(browserOdpManagerB.eventManager).not.toBe(null); - }); - - // it("should call event manager's sendEvent if ODP Event is valid", async () => { - // const browserOdpManager = new BrowserOdpManager({ - // odpOptions: { - // eventManager: fakeEventManager, - // }, - // }); - - // const odpConfig = new OdpConfig('key', 'host', []); - - // browserOdpManager.updateSettings(odpConfig); - - // // Test Valid OdpEvent - calls event manager with valid OdpEvent object - // const validIdentifiers = new Map(); - // validIdentifiers.set('vuid', vuidA); - - // const validOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, validIdentifiers); - - // await browserOdpManager.sendEvent(validOdpEvent); - // verify(mockEventManager.sendEvent(anything())).once(); - - // // Test Invalid OdpEvents - logs error and short circuits - // // Does not include `vuid` in identifiers does not have a local this.vuid populated in BrowserOdpManager - // browserOdpManager.vuid = undefined; - // const invalidOdpEvent = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED, undefined); - - // await expect(browserOdpManager.sendEvent(invalidOdpEvent)).rejects.toThrow( - // ERROR_MESSAGES.ODP_SEND_EVENT_FAILED_VUID_MISSING - // ); - // }); - describe('Populates BrowserOdpManager correctly with all odpOptions', () => { - it('odpOptions.disabled = true disables BrowserOdpManager', () => { - const odpOptions: OdpOptions = { - disabled: true, - }; + beforeAll(() => { - const browserOdpManager = new BrowserOdpManager({ - odpOptions, - }); - - expect(browserOdpManager.enabled).toBe(false); }); it('Custom odpOptions.segmentsCache overrides default LRUCache', () => { @@ -289,17 +132,19 @@ describe('OdpManager', () => { maxSize: 2, timeout: 4000, }), - }; + }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); + const segmentManager = browserOdpManager['segmentManager'] as OdpSegmentManager; + // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(2); + expect(browserOdpManager.segmentManager._segmentsCache.maxSize).toBe(2); // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(4000); + expect(browserOdpManager.segmentManager._segmentsCache.timeout).toBe(4000); }); it('Custom odpOptions.segmentsCacheSize overrides default LRUCache size', () => { @@ -307,12 +152,12 @@ describe('OdpManager', () => { segmentsCacheSize: 2, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.maxSize).toBe(2); + expect(browserOdpManager.segmentManager._segmentsCache.maxSize).toBe(2); }); it('Custom odpOptions.segmentsCacheTimeout overrides default LRUCache timeout', () => { @@ -320,12 +165,12 @@ describe('OdpManager', () => { segmentsCacheTimeout: 4000, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); // @ts-ignore - expect(browserOdpManager.segmentManager?._segmentsCache.timeout).toBe(4000); + expect(browserOdpManager.segmentManager._segmentsCache.timeout).toBe(4000); }); it('Custom odpOptions.segmentsApiTimeout overrides default Segment API Request Handler timeout', () => { @@ -333,7 +178,7 @@ describe('OdpManager', () => { segmentsApiTimeout: 4000, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -342,7 +187,7 @@ describe('OdpManager', () => { }); it('Browser default Segments API Request Handler timeout should be used when odpOptions does not include segmentsApiTimeout', () => { - const browserOdpManager = new BrowserOdpManager({}); + const browserOdpManager = BrowserOdpManager.createInstance({}); // @ts-ignore expect(browserOdpManager.segmentManager.odpSegmentApiManager.requestHandler.timeout).toBe(10000); @@ -353,7 +198,7 @@ describe('OdpManager', () => { segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 4000), }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -367,7 +212,7 @@ describe('OdpManager', () => { segmentsRequestHandler: new BrowserRequestHandler(fakeLogger, 1), }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -377,16 +222,17 @@ describe('OdpManager', () => { it('Custom odpOptions.segmentManager overrides default Segment Manager', () => { const customSegmentManager = new OdpSegmentManager( - odpConfig, new BrowserLRUCache(), - fakeSegmentApiManager + fakeSegmentApiManager, + fakeLogger, + odpConfig, ); const odpOptions: OdpOptions = { segmentManager: customSegmentManager, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -396,12 +242,13 @@ describe('OdpManager', () => { it('Custom odpOptions.segmentManager override takes precedence over all other segments-related odpOptions', () => { const customSegmentManager = new OdpSegmentManager( - odpConfig, new BrowserLRUCache({ maxSize: 1, timeout: 1, }), - new OdpSegmentApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger) + new OdpSegmentApiManager(new BrowserRequestHandler(fakeLogger, 1), fakeLogger), + fakeLogger, + odpConfig, ); const odpOptions: OdpOptions = { @@ -413,7 +260,7 @@ describe('OdpManager', () => { segmentManager: customSegmentManager, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -435,7 +282,7 @@ describe('OdpManager', () => { eventApiTimeout: 4000, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -446,7 +293,7 @@ describe('OdpManager', () => { it('Browser default Events API Request Handler timeout should be used when odpOptions does not include eventsApiTimeout', () => { const odpOptions: OdpOptions = {}; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -459,7 +306,7 @@ describe('OdpManager', () => { eventFlushInterval: 4000, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -470,7 +317,7 @@ describe('OdpManager', () => { it('Default ODP event flush interval is used when odpOptions does not include eventFlushInterval', () => { const odpOptions: OdpOptions = {}; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -483,7 +330,7 @@ describe('OdpManager', () => { eventFlushInterval: 0, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -499,7 +346,7 @@ describe('OdpManager', () => { eventBatchSize: 2, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -512,7 +359,7 @@ describe('OdpManager', () => { eventQueueSize: 2, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -525,7 +372,7 @@ describe('OdpManager', () => { eventRequestHandler: new BrowserRequestHandler(fakeLogger, 4000), }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -542,7 +389,7 @@ describe('OdpManager', () => { eventRequestHandler: new BrowserRequestHandler(fakeLogger, 1), }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -566,7 +413,7 @@ describe('OdpManager', () => { eventManager: customEventManager, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -604,7 +451,7 @@ describe('OdpManager', () => { eventManager: customEventManager, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); @@ -642,7 +489,7 @@ describe('OdpManager', () => { eventQueueSize: 4, }; - const browserOdpManager = new BrowserOdpManager({ + const browserOdpManager = BrowserOdpManager.createInstance({ odpOptions, }); diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index c1bac8f08..8b99b5752 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -449,7 +449,7 @@ describe('OdpManager', () => { verify(mockEventManager.stop()).once(); }); - it('should call eventManager.identifyUser with correct parameters when identifyUser is called', () => { + it('should call eventManager.identifyUser with correct parameters when identifyUser is called', async () => { const odpIntegrationConfig: OdpIntegratedConfig = { integrated: true, odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) @@ -463,8 +463,7 @@ describe('OdpManager', () => { vuidEnabled: true, }); - - odpManager.updateSettings(odpIntegrationConfig); + await odpManager.onReady(); const userId = 'user123'; const vuid = 'vuid_123'; @@ -485,7 +484,7 @@ describe('OdpManager', () => { expect(vuidArg3).toEqual(vuid); }); - it('should send event with correct parameters', () => { + it('should send event with correct parameters', async () => { const odpIntegrationConfig: OdpIntegratedConfig = { integrated: true, odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) @@ -499,7 +498,7 @@ describe('OdpManager', () => { vuidEnabled: true, }); - odpManager.updateSettings(odpIntegrationConfig); + await odpManager.onReady(); const identifiers = new Map([['email', 'a@b.com']]); const data = new Map([['key1', 'value1'], ['key2', 'value2']]); @@ -532,7 +531,7 @@ describe('OdpManager', () => { }); - it('should throw an error if event action is empty string and not call eventManager', () => { + it('should throw an error if event action is empty string and not call eventManager', async () => { const odpIntegrationConfig: OdpIntegratedConfig = { integrated: true, odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) @@ -546,7 +545,7 @@ describe('OdpManager', () => { vuidEnabled: true, }); - odpManager.updateSettings(odpIntegrationConfig); + await odpManager.onReady(); const identifiers = new Map([['email', 'a@b.com']]); const data = new Map([['key1', 'value1'], ['key2', 'value2']]); @@ -561,5 +560,122 @@ describe('OdpManager', () => { expect(sendEvent).toThrow('ODP action is not valid'); verify(mockEventManager.sendEvent(anything())).never(); }); + + it('should throw an error if event data is invalid', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', {}]]); + + const sendEvent = () => odpManager.sendEvent({ + action: 'action', + type: 'type', + identifiers, + data, + }); + + expect(sendEvent).toThrow(ERROR_MESSAGES.ODP_INVALID_DATA); + verify(mockEventManager.sendEvent(anything())).never(); + }); + + it('should stop itself and eventManager if stop is called', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + odpManager.stop(); + + expect(odpManager.getStatus()).toEqual(Status.Stopped); + verify(mockEventManager.stop()).once(); + }); + + it('should drop relevant calls and log error when odpIntegrationConfig is not available', async () => { + const odpManager = testOdpManager({ + odpIntegrationConfig: undefined, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + const segments = await odpManager.fetchQualifiedSegments('vuid_user1', []); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).once(); + expect(segments).toBeNull(); + + odpManager.identifyUser('vuid_user1'); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).twice(); + verify(mockEventManager.identifyUser(anything(), anything())).never(); + + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', {}]]); + + odpManager.sendEvent({ + action: 'action', + type: 'type', + identifiers, + data, + }); + + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE)).thrice(); + verify(mockEventManager.sendEvent(anything())).never(); + + }); + + it('should drop relevant calls and log error when odp is not integrated', async () => { + const odpManager = testOdpManager({ + odpIntegrationConfig: { integrated: false }, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + const segments = await odpManager.fetchQualifiedSegments('vuid_user1', []); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).once(); + expect(segments).toBeNull(); + + odpManager.identifyUser('vuid_user1'); + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).twice(); + verify(mockEventManager.identifyUser(anything(), anything())).never(); + + const identifiers = new Map([['email', 'a@b.com']]); + const data = new Map([['key1', {}]]); + + odpManager.sendEvent({ + action: 'action', + type: 'type', + identifiers, + data, + }); + + verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED)).thrice(); + verify(mockEventManager.sendEvent(anything())).never(); + }); }); From 3331784cc5bd5d84af20e69490733846b56e8484 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Wed, 3 Apr 2024 01:34:29 +0600 Subject: [PATCH 14/24] fix project config tests --- lib/core/project_config/index.tests.js | 49 +++++++++----------------- lib/core/project_config/index.ts | 6 ++-- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/lib/core/project_config/index.tests.js b/lib/core/project_config/index.tests.js index 0500e8884..82c96b6d1 100644 --- a/lib/core/project_config/index.tests.js +++ b/lib/core/project_config/index.tests.js @@ -813,21 +813,12 @@ describe('lib/core/project_config', function() { assert.equal(config.integrations.length, 4); }); - it('should populate the public key value from the odp integration', () => { - assert.exists(config.publicKeyForOdp); - }); - - it('should populate the host value from the odp integration', () => { - assert.exists(config.hostForOdp); - }); - - it('should populate the pixelUrl value from the odp integration', () => { - assert.exists(config.pixelUrlForOdp); - }); - - it('should contain all expected unique odp segments in allSegments', () => { - assert.equal(config.allSegments.length, 3); - assert.deepEqual(config.allSegments, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']); + it('should populate odpIntegrationConfig', () => { + assert.isTrue(config.odpIntegrationConfig.integrated); + assert.equal(config.odpIntegrationConfig.odpConfig.apiKey, 'W4WzcEs-ABgXorzY7h1LCQ'); + assert.equal(config.odpIntegrationConfig.odpConfig.apiHost, 'https://api.zaius.com'); + assert.equal(config.odpIntegrationConfig.odpConfig.pixelUrl, 'https://jumbe.zaius.com'); + assert.deepEqual(config.odpIntegrationConfig.odpConfig.segmentsToCheck, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']); }); }); @@ -842,23 +833,12 @@ describe('lib/core/project_config', function() { assert.equal(config.integrations.length, 3); }); - it('should populate the public key value from the odp integration', () => { - assert.exists(config.publicKeyForOdp); - assert.equal(config.publicKeyForOdp, 'W4WzcEs-ABgXorzY7h1LCQ'); - }); - - it('should populate the host value from the odp integration', () => { - assert.exists(config.hostForOdp); - assert.equal(config.hostForOdp, 'https://api.zaius.com'); - }); - - it('should populate the pixelUrl value from the odp integration', () => { - assert.exists(config.pixelUrlForOdp); - assert.equal(config.pixelUrlForOdp, 'https://jumbe.zaius.com'); - }); - - it('should contain all expected unique odp segments in all segments', () => { - assert.equal(config.allSegments.length, 0); + it('should populate odpIntegrationConfig', () => { + assert.isTrue(config.odpIntegrationConfig.integrated); + assert.equal(config.odpIntegrationConfig.odpConfig.apiKey, 'W4WzcEs-ABgXorzY7h1LCQ'); + assert.equal(config.odpIntegrationConfig.odpConfig.apiHost, 'https://api.zaius.com'); + assert.equal(config.odpIntegrationConfig.odpConfig.pixelUrl, 'https://jumbe.zaius.com'); + assert.deepEqual(config.odpIntegrationConfig.odpConfig.segmentsToCheck, []); }); }); @@ -882,6 +862,11 @@ describe('lib/core/project_config', function() { it('should convert integrations from the datafile into the project config', () => { assert.equal(config.integrations.length, 0); }); + + it('should populate odpIntegrationConfig', () => { + assert.isFalse(config.odpIntegrationConfig.integrated); + assert.isUndefined(config.odpIntegrationConfig.odpConfig); + }); }); }); }); diff --git a/lib/core/project_config/index.ts b/lib/core/project_config/index.ts index aeedff18c..d2fa02bba 100644 --- a/lib/core/project_config/index.ts +++ b/lib/core/project_config/index.ts @@ -200,9 +200,9 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str if (integration.key === 'odp') { odpIntegrated = true; - odpApiKey = integration.publicKey || ''; - odpApiHost = integration.host || ''; - odpPixelUrl = integration.pixelUrl || ''; + odpApiKey = odpApiKey || integration.publicKey || ''; + odpApiHost = odpApiHost || integration.host || ''; + odpPixelUrl = odpPixelUrl || integration.pixelUrl || ''; } }); } From 6506d3703db017734e470d086215af112d3dbe9d Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Thu, 4 Apr 2024 02:39:25 +0600 Subject: [PATCH 15/24] fix tests --- lib/core/odp/odp_manager.ts | 8 +- .../project_config_manager.tests.js | 98 +++++++++++++------ .../project_config/project_config_manager.ts | 6 +- lib/index.browser.tests.js | 12 ++- lib/optimizely/index.tests.js | 23 ----- lib/optimizely/index.ts | 4 +- 6 files changed, 86 insertions(+), 65 deletions(-) diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 773c8fb9f..18682a428 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -110,21 +110,23 @@ export abstract class OdpManager implements IOdpManager { eventManager: IOdpEventManager; logger: LogHandler; }) { + console.log('odp manager constructor', odpIntegrationConfig); this.segmentManager = segmentManager; this.eventManager = eventManager; this.logger = logger; this.configPromise = resolvablePromise(); - const readineessDependencies: PromiseLike[] = [this.configPromise]; + const readinessDependencies: PromiseLike[] = [this.configPromise]; if (this.isVuidEnabled()) { - readineessDependencies.push(this.initializeVuid()); + readinessDependencies.push(this.initializeVuid()); } - this.initPromise = Promise.all(readineessDependencies); + this.initPromise = Promise.all(readinessDependencies); this.onReady().then(() => { + console.log('odp got ready'); this.ready = true; if(this.isVuidEnabled()) { this.registerVuid(); diff --git a/lib/core/project_config/project_config_manager.tests.js b/lib/core/project_config/project_config_manager.tests.js index bc44e62eb..f77f6b8d3 100644 --- a/lib/core/project_config/project_config_manager.tests.js +++ b/lib/core/project_config/project_config_manager.tests.js @@ -368,58 +368,92 @@ describe('lib/core/project_config/project_config_manager', function() { }); describe('when constructed with sdkKey and with a valid datafile object', function() { - it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners after becoming ready', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); + it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners if datafile does not change', async function() { var configWithFeatures = testData.getTestProjectConfigWithFeatures(); + + const handlers = []; + const mockDatafileManager = { + start: () => {}, + get: () => JSON.stringify(configWithFeatures), + on: (event, fn) => handlers.push(fn), + onReady: () => Promise.resolve(), + pushUpdate: (datafile) => handlers.forEach(handler => handler({ datafile })), + }; + var manager = projectConfigManager.createProjectConfigManager({ datafile: configWithFeatures, sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, configWithFeatures), + datafileManager: mockDatafileManager, }); var onUpdateSpy = sinon.spy(); manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - // Datafile is the same as what it was constructed with, so should - // not have called update listener - sinon.assert.notCalled(onUpdateSpy); + + const result = await manager.onReady(); + assert.include(result, { + success: true, }); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + // allow queued microtasks to run + await Promise.resolve(); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + + configWithFeatures.revision = '99'; + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + sinon.assert.callCount(onUpdateSpy, 2); }); }); describe('when constructed with sdkKey and with a valid datafile string', function() { - it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners after becoming ready', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); + it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners if datafile does not change', async function() { var configWithFeatures = testData.getTestProjectConfigWithFeatures(); + + const handlers = []; + const mockDatafileManager = { + start: () => {}, + get: () => JSON.stringify(configWithFeatures), + on: (event, fn) => handlers.push(fn), + onReady: () => Promise.resolve(), + pushUpdate: (datafile) => handlers.forEach(handler => handler({ datafile })), + }; + var manager = projectConfigManager.createProjectConfigManager({ datafile: JSON.stringify(configWithFeatures), sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, JSON.stringify(configWithFeatures)), + datafileManager: mockDatafileManager, }); var onUpdateSpy = sinon.spy(); manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - // Datafile is the same as what it was constructed with, so should - // not have called update listener - sinon.assert.notCalled(onUpdateSpy); + + const result = await manager.onReady(); + assert.include(result, { + success: true, }); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + // allow queued microtasks to run + await Promise.resolve(); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + + configWithFeatures.revision = '99'; + mockDatafileManager.pushUpdate(JSON.stringify(configWithFeatures)); + await Promise.resolve(); + + sinon.assert.callCount(onUpdateSpy, 2); }); }); diff --git a/lib/core/project_config/project_config_manager.ts b/lib/core/project_config/project_config_manager.ts index 0432b5fc1..cd311a3cf 100644 --- a/lib/core/project_config/project_config_manager.ts +++ b/lib/core/project_config/project_config_manager.ts @@ -89,6 +89,7 @@ export class ProjectConfigManager { if (config.sdkKey && config.datafileManager) { this.datafileManager = config.datafileManager; this.datafileManager.start(); + this.readyPromise = this.datafileManager .onReady() .then(this.onDatafileManagerReadyFulfill.bind(this), this.onDatafileManagerReadyReject.bind(this)); @@ -188,7 +189,10 @@ export class ProjectConfigManager { if (configObj && oldRevision !== configObj.revision) { this.configObj = configObj; this.optimizelyConfigObj = null; - this.updateListeners.forEach(listener => listener(configObj)); + + queueMicrotask(() => { + this.updateListeners.forEach(listener => listener(configObj)); + }); } } diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 04e4a1559..981a79b2d 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -759,7 +759,7 @@ describe('javascript-sdk (Browser)', function() { }; const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + datafile: testData.getOdpIntegratedConfigWithSegments(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -769,9 +769,10 @@ describe('javascript-sdk (Browser)', function() { }, }); + const readyData = await client.onReady(); + sinon.assert.called(fakeSegmentManager.updateSettings); - const readyData = await client.onReady(); assert.equal(readyData.success, true); assert.isUndefined(readyData.reason); @@ -794,7 +795,7 @@ describe('javascript-sdk (Browser)', function() { }; const client = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), + datafile: testData.getOdpIntegratedConfigWithSegments(), errorHandler: fakeErrorHandler, eventDispatcher: fakeEventDispatcher, eventBatchSize: null, @@ -1143,7 +1144,7 @@ describe('javascript-sdk (Browser)', function() { }); it('should send odp client_initialized on client instantiation', async () => { - const odpConfig = new OdpConfig(); + const odpConfig = new OdpConfig('key', 'host', 'pixel', []); const apiManager = new BrowserOdpEventApiManager(mockRequestHandler, logger); sinon.spy(apiManager, 'sendEvents'); const eventManager = new BrowserOdpEventManager({ @@ -1170,7 +1171,8 @@ describe('javascript-sdk (Browser)', function() { clock.tick(100); - const [events] = apiManager.sendEvents.getCall(0).args; + const [_, events] = apiManager.sendEvents.getCall(0).args; + const [firstEvent] = events; assert.equal(firstEvent.action, 'client_initialized'); assert.equal(firstEvent.type, 'fullstack'); diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 39e6c3c6e..1624df534 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -10144,29 +10144,6 @@ describe('lib/optimizely', function() { fns.uuid.restore(); }); - it('should call logger with log level of "info" when odp disabled', () => { - new Optimizely({ - clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - eventProcessor, - notificationCenter, - odpManager: new NodeOdpManager({ - logger: createdLogger, - odpOptions: { - disabled: true, - }, - }), - }); - - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, LOG_MESSAGES.ODP_DISABLED); - }); - it('should send an identify event when called with odp enabled', () => { // TODO }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 531f0db63..a14cc8467 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -110,6 +110,7 @@ export default class Optimizely implements Client { this.errorHandler = config.errorHandler; this.isOptimizelyConfigValid = config.isValidInstance; this.logger = config.logger; + this.odpManager = config.odpManager; let decideOptionsArray = config.defaultDecideOptions ?? []; if (!Array.isArray(decideOptionsArray)) { @@ -135,6 +136,7 @@ export default class Optimizely implements Client { }); this.disposeOnUpdate = this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { + console.log('config updated'); this.logger.log( LOG_LEVEL.INFO, LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, @@ -1441,7 +1443,7 @@ export default class Optimizely implements Client { * null if provided inputs are invalid */ createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { - let userIdentifier = userId || this.odpManager?.getVuid(); + let userIdentifier = userId ?? this.odpManager?.getVuid(); if ( userIdentifier === undefined || From 969542340b082d13292083e6788afc36d6a27b73 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Thu, 4 Apr 2024 02:45:43 +0600 Subject: [PATCH 16/24] fix --- lib/index.browser.tests.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index 981a79b2d..c3bbb5a4f 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -639,7 +639,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ + odpManager: BrowserOdpManager.createInstance({ logger, odpOptions: { disabled: true, @@ -657,7 +657,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ + odpManager: BrowserOdpManager.createInstance({ logger, }), }); @@ -689,7 +689,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ + odpManager: BrowserOdpManager.createInstance({ logger, odpOptions: { segmentsCacheSize: 10, @@ -711,7 +711,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, - odpManager: new BrowserOdpManager({ + odpManager: BrowserOdpManager.createInstance({ logger, odpOptions: { segmentsCacheTimeout: 10, From e8727504a9bf4ea114d83bc69f75f522c418a38f Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Thu, 4 Apr 2024 06:01:14 +0600 Subject: [PATCH 17/24] config manager tests fix --- .../project_config_manager.tests.js | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/lib/core/project_config/project_config_manager.tests.js b/lib/core/project_config/project_config_manager.tests.js index f77f6b8d3..e9b9322f5 100644 --- a/lib/core/project_config/project_config_manager.tests.js +++ b/lib/core/project_config/project_config_manager.tests.js @@ -165,7 +165,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); }); - it('does not call onUpdate listeners after becoming ready when constructed with a valid datafile and without sdkKey', function() { + it('calls onUpdate listeners once when constructed with a valid datafile and without sdkKey', function() { var configWithFeatures = testData.getTestProjectConfigWithFeatures(); var manager = projectConfigManager.createProjectConfigManager({ datafile: configWithFeatures, @@ -173,7 +173,7 @@ describe('lib/core/project_config/project_config_manager', function() { var onUpdateSpy = sinon.spy(); manager.onUpdate(onUpdateSpy); return manager.onReady().then(function() { - sinon.assert.notCalled(onUpdateSpy); + sinon.assert.calledOnce(onUpdateSpy); }); }); @@ -242,7 +242,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); }); - it('calls onUpdate listeners after becoming ready, and after the datafile manager emits updates', function() { + it('calls onUpdate listeners after becoming ready, and after the datafile manager emits updates', async function() { datafileManager.HttpPollingDatafileManager.returns({ start: sinon.stub(), stop: sinon.stub(), @@ -256,21 +256,20 @@ describe('lib/core/project_config/project_config_manager', function() { }); var onUpdateSpy = sinon.spy(); manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function() { - sinon.assert.calledOnce(onUpdateSpy); - - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - - updateListener({ datafile: newDatafile }); - sinon.assert.calledTwice(onUpdateSpy); - }); + await manager.onReady(); + sinon.assert.calledOnce(onUpdateSpy); + var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; + var updateListener = fakeDatafileManager.on.getCall(0).args[1]; + var newDatafile = testData.getTestProjectConfigWithFeatures(); + newDatafile.revision = '36'; + fakeDatafileManager.get.returns(newDatafile); + updateListener({ datafile: newDatafile }); + + await Promise.resolve(); + sinon.assert.calledTwice(onUpdateSpy); }); - it('can remove onUpdate listeners using the function returned from onUpdate', function() { + it('can remove onUpdate listeners using the function returned from onUpdate', async function() { datafileManager.HttpPollingDatafileManager.returns({ start: sinon.stub(), stop: sinon.stub(), @@ -282,29 +281,29 @@ describe('lib/core/project_config/project_config_manager', function() { sdkKey: '12345', datafileManager: createHttpPollingDatafileManager('12345', logger), }); - return manager.onReady().then(function() { - var onUpdateSpy = sinon.spy(); - var unsubscribe = manager.onUpdate(onUpdateSpy); + await manager.onReady(); + var onUpdateSpy = sinon.spy(); + var unsubscribe = manager.onUpdate(onUpdateSpy); + var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; + var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - - sinon.assert.calledOnce(onUpdateSpy); - - unsubscribe(); - - newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '37'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - // // Should not call onUpdateSpy again since we unsubscribed - updateListener({ datafile: testData.getTestProjectConfigWithFeatures() }); - sinon.assert.calledOnce(onUpdateSpy); - }); + var newDatafile = testData.getTestProjectConfigWithFeatures(); + newDatafile.revision = '36'; + fakeDatafileManager.get.returns(newDatafile); + + updateListener({ datafile: newDatafile }); + // allow queued micortasks to run + await Promise.resolve(); + + sinon.assert.calledOnce(onUpdateSpy); + unsubscribe(); + newDatafile = testData.getTestProjectConfigWithFeatures(); + newDatafile.revision = '37'; + fakeDatafileManager.get.returns(newDatafile); + updateListener({ datafile: newDatafile }); + // // Should not call onUpdateSpy again since we unsubscribed + updateListener({ datafile: testData.getTestProjectConfigWithFeatures() }); + sinon.assert.calledOnce(onUpdateSpy); }); it('fulfills its ready promise with an unsuccessful result when the datafile manager emits an invalid datafile', function() { From 7f84ae46e6ebe4617ae6b8e33397b7f01a318208 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Thu, 4 Apr 2024 07:14:54 +0600 Subject: [PATCH 18/24] odpManager test --- lib/core/odp/odp_manager.ts | 23 ++++++++++++---- lib/index.browser.tests.js | 6 ++++- lib/optimizely/index.ts | 3 +-- tests/odpManager.spec.ts | 54 +++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 18682a428..f45608a1e 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -66,7 +66,7 @@ export abstract class OdpManager implements IOdpManager { */ private initPromise: Promise; - private ready: boolean = false; + private ready = false; /** * Promise that resolves when odpConfig becomes available @@ -110,7 +110,6 @@ export abstract class OdpManager implements IOdpManager { eventManager: IOdpEventManager; logger: LogHandler; }) { - console.log('odp manager constructor', odpIntegrationConfig); this.segmentManager = segmentManager; this.eventManager = eventManager; this.logger = logger; @@ -126,9 +125,8 @@ export abstract class OdpManager implements IOdpManager { this.initPromise = Promise.all(readinessDependencies); this.onReady().then(() => { - console.log('odp got ready'); this.ready = true; - if(this.isVuidEnabled()) { + if(this.isVuidEnabled() && this.status === Status.Running) { this.registerVuid(); } }); @@ -213,12 +211,15 @@ export abstract class OdpManager implements IOdpManager { * @returns {Promise} A promise holding either a list of qualified segments or null. */ async fetchQualifiedSegments(userId: string, options: Array = []): Promise { + console.log('fetch woot'); if (!this.odpIntegrationConfig) { + console.log('wat no config '); this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); return null; } if (!this.odpIntegrationConfig.integrated) { + console.log('wat no integration '); this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); return null; } @@ -227,7 +228,9 @@ export abstract class OdpManager implements IOdpManager { return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, userId, options); } - return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); + const foo = await this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); + console.log('foo is ', foo); + return foo; } /** @@ -302,6 +305,16 @@ export abstract class OdpManager implements IOdpManager { } private registerVuid() { + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + return; + } + + if (!this.odpIntegrationConfig.integrated) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); + return; + } + const vuid = this.getVuid(); if (!vuid) { return; diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index c3bbb5a4f..e9b5283e5 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -582,6 +582,7 @@ describe('javascript-sdk (Browser)', function() { var sandbox = sinon.sandbox.create(); const fakeOptimizely = { + onReady: () => Promise.resolve({ success: true }), identifyUser: sinon.stub().returns(), }; @@ -621,12 +622,14 @@ describe('javascript-sdk (Browser)', function() { requestParams.clear(); }); - it('should send identify event by default when initialized', () => { + it('should send identify event by default when initialized', async () => { new OptimizelyUserContext({ optimizely: fakeOptimizely, userId: testFsUserId, }); + await fakeOptimizely.onReady(); + sinon.assert.calledOnce(fakeOptimizely.identifyUser); sinon.assert.calledWith(fakeOptimizely.identifyUser, testFsUserId); @@ -639,6 +642,7 @@ describe('javascript-sdk (Browser)', function() { eventDispatcher: fakeEventDispatcher, eventBatchSize: null, logger, + odpOptions: { disabled: true }, odpManager: BrowserOdpManager.createInstance({ logger, odpOptions: { diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index a14cc8467..e29d04daa 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -136,7 +136,6 @@ export default class Optimizely implements Client { }); this.disposeOnUpdate = this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { - console.log('config updated'); this.logger.log( LOG_LEVEL.INFO, LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, @@ -1443,7 +1442,7 @@ export default class Optimizely implements Client { * null if provided inputs are invalid */ createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext | null { - let userIdentifier = userId ?? this.odpManager?.getVuid(); + const userIdentifier = userId ?? this.odpManager?.getVuid(); if ( userIdentifier === undefined || diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index 8b99b5752..53941e7db 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -449,6 +449,25 @@ describe('OdpManager', () => { verify(mockEventManager.stop()).once(); }); + it('should register vuid after becoming ready if odp is integrated', async () => { + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager, + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + verify(mockEventManager.registerVuid(anything())).once(); + }); + it('should call eventManager.identifyUser with correct parameters when identifyUser is called', async () => { const odpIntegrationConfig: OdpIntegratedConfig = { integrated: true, @@ -591,6 +610,39 @@ describe('OdpManager', () => { verify(mockEventManager.sendEvent(anything())).never(); }); + it.only('should fetch qualified segments correctly for both fs_user_id and vuid', async () => { + const userId = 'user123'; + const vuid = 'vuid_123'; + + when(mockSegmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, anything())) + .thenResolve(['fs1', 'fs2']); + + when(mockSegmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, vuid, anything())) + .thenResolve(['vuid1', 'vuid2']); + + const odpIntegrationConfig: OdpIntegratedConfig = { + integrated: true, + odpConfig: new OdpConfig(keyA, hostA, pixelA, segmentsA) + }; + + const odpManager = testOdpManager({ + odpIntegrationConfig, + segmentManager: instance(mockSegmentManager), + eventManager, + logger, + vuidEnabled: true, + }); + + await odpManager.onReady(); + + const fsSegments = await odpManager.fetchQualifiedSegments(userId); + expect(fsSegments).toEqual(['fs1', 'fs2']); + + const vuidSegments = await odpManager.fetchQualifiedSegments(vuid); + expect(vuidSegments).toEqual(['vuid1', 'vuid2']); + }); + + it('should stop itself and eventManager if stop is called', async () => { const odpIntegrationConfig: OdpIntegratedConfig = { integrated: true, @@ -613,6 +665,8 @@ describe('OdpManager', () => { verify(mockEventManager.stop()).once(); }); + + it('should drop relevant calls and log error when odpIntegrationConfig is not available', async () => { const odpManager = testOdpManager({ odpIntegrationConfig: undefined, From 12623c016db5c2cd29c36f43fecc7fdcfb8cfc63 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Thu, 4 Apr 2024 15:12:18 +0600 Subject: [PATCH 19/24] event man update --- lib/core/odp/odp_event_manager.ts | 13 +++-- tests/odpEventManager.spec.ts | 85 +++++++++++++++++++++++-------- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index 8c277a692..d6b702de9 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -227,7 +227,11 @@ export abstract class OdpEventManager implements IOdpEventManager { } this.status = Status.Running; - this.setNewTimeout(); + + // no need of periodic flush if batchSize is 1 + if (this.batchSize > 1) { + this.setNewTimeout(); + } } /** @@ -301,6 +305,7 @@ export abstract class OdpEventManager implements IOdpEventManager { this.logger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.'); return; } + const hasNecessaryIdentifiers = this.hasNecessaryIdentifiers; if (!this.hasNecessaryIdentifiers(event)) { this.logger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.'); @@ -317,7 +322,6 @@ export abstract class OdpEventManager implements IOdpEventManager { } this.queue.push(event); - this.processQueue(); } @@ -347,7 +351,10 @@ export abstract class OdpEventManager implements IOdpEventManager { } } - this.setNewTimeout(); + // no need for periodic flush if batchSize is 1 + if (this.batchSize > 1) { + this.setNewTimeout(); + } } /** diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index dc71119e3..35fbdcf44 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -18,7 +18,8 @@ import { ODP_EVENT_ACTION, ODP_DEFAULT_EVENT_TYPE, ERROR_MESSAGES } from '../lib import { OdpConfig } from '../lib/core/odp/odp_config'; import { Status } from '../lib/core/odp/odp_event_manager'; import { BrowserOdpEventManager } from "../lib/plugins/odp/event_manager/index.browser"; -import { NodeOdpEventManager, NodeOdpEventManager as OdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; +import { NodeOdpEventManager } from '../lib/plugins/odp/event_manager/index.node'; +import { OdpEventManager } from '../lib/core/odp/odp_event_manager'; import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; import { IOdpEventApiManager } from '../lib/core/odp/odp_event_api_manager'; import { LogHandler, LogLevel } from '../lib/modules/logging'; @@ -136,6 +137,20 @@ const abortableRequest = (statusCode: number, body: string) => { }; }; +class TestOdpEventManager extends OdpEventManager { + constructor(options: any) { + super(options); + } + protected initParams(batchSize: number, queueSize: number, flushInterval: number): void { + this.queueSize = queueSize; + this.batchSize = batchSize; + this.flushInterval = flushInterval; + } + protected discardEventsIfNeeded(): void { + } + protected hasNecessaryIdentifiers = (event: OdpEvent): boolean => event.identifiers.size >= 0; +} + describe('OdpEventManager', () => { let mockLogger: LogHandler; let mockApiManager: IOdpEventApiManager; @@ -159,7 +174,7 @@ describe('OdpEventManager', () => { }); it('should log an error and not start if start() is called without a config', () => { - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig: undefined, apiManager, logger, @@ -173,7 +188,7 @@ describe('OdpEventManager', () => { }); it('should start() correctly after odpConfig is provided', () => { - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -188,7 +203,7 @@ describe('OdpEventManager', () => { }); it('should log and discard events when event manager is not running', () => { - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -203,7 +218,7 @@ describe('OdpEventManager', () => { }); it('should discard events with invalid data', () => { - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -232,7 +247,7 @@ describe('OdpEventManager', () => { it('should log a max queue hit and discard ', () => { // set queue to maximum of 1 - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -253,7 +268,7 @@ describe('OdpEventManager', () => { }); it('should add additional information to each event', () => { - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -278,30 +293,56 @@ describe('OdpEventManager', () => { expect(eventData.get('key-4')).toEqual(processedEventData.get('key-4')); }); - it('should attempt to flush an empty queue at flush intervals', async () => { - const eventManager = new OdpEventManager({ + it('should attempt to flush an empty queue at flush intervals if batchSize is greater than 1', async () => { + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, clientEngine, clientVersion, + batchSize: 10, flushInterval: 100, }); - const spiedEventManager = spy(eventManager); + //@ts-ignore + const processQueueSpy = jest.spyOn(eventManager, 'processQueue'); eventManager.start(); // do not add events to the queue, but allow for... jest.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) - verify(spiedEventManager['processQueue'](anything())).atLeast(3); + expect(processQueueSpy).toHaveBeenCalledTimes(3); + }); + + + it('should not flush periodically if batch size is 1', async () => { + const eventManager = new TestOdpEventManager({ + odpConfig, + apiManager, + logger, + clientEngine, + clientVersion, + batchSize: 1, + flushInterval: 100, + }); + + //@ts-ignore + const processQueueSpy = jest.spyOn(eventManager, 'processQueue'); + + eventManager.start(); + eventManager.sendEvent(EVENTS[0]); + eventManager.sendEvent(EVENTS[1]); + + jest.advanceTimersByTime(350); // 3 flush intervals executions (giving a little longer) + + expect(processQueueSpy).toHaveBeenCalledTimes(2); }); it('should dispatch events in correct batch sizes', async () => { when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -329,7 +370,7 @@ describe('OdpEventManager', () => { }); it('should dispatch events with correct payload', async () => { - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -354,7 +395,7 @@ describe('OdpEventManager', () => { }); it('should dispatch events with correct odpConfig', async () => { - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -385,7 +426,7 @@ describe('OdpEventManager', () => { } } - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -415,7 +456,7 @@ describe('OdpEventManager', () => { const retries = 3; const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -443,7 +484,7 @@ describe('OdpEventManager', () => { when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -471,7 +512,7 @@ describe('OdpEventManager', () => { it('should flush all queued events before stopping', async () => { when(mockApiManager.sendEvents(anything(), anything())).thenResolve(false); const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -503,7 +544,7 @@ describe('OdpEventManager', () => { const updatedConfig = new OdpConfig('new-key', 'new-host', 'https://new-odp.pixel.com', []); const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -537,7 +578,7 @@ describe('OdpEventManager', () => { const updatedConfig = new OdpConfig('new-key', 'new-host', 'https://new-odp.pixel.com', []); const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -579,7 +620,7 @@ describe('OdpEventManager', () => { const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, @@ -615,7 +656,7 @@ describe('OdpEventManager', () => { const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ + const eventManager = new TestOdpEventManager({ odpConfig, apiManager, logger, From 7e9a276539f036eb8e33f219ddbb7c08eb351888 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Thu, 4 Apr 2024 15:24:06 +0600 Subject: [PATCH 20/24] copyright --- lib/core/odp/odp_event_api_manager.ts | 2 +- lib/core/odp/odp_event_manager.ts | 2 +- lib/core/odp/odp_manager.ts | 2 +- lib/core/odp/odp_segment_manager.ts | 2 +- lib/core/project_config/index.tests.js | 2 +- lib/core/project_config/index.ts | 2 +- .../project_config_manager.tests.js | 2 +- .../project_config/project_config_manager.ts | 2 +- lib/index.node.ts | 2 +- lib/optimizely/index.tests.js | 2 +- lib/optimizely_user_context/index.ts | 2 +- .../odp/event_api_manager/index.browser.ts | 16 ++++++++++++++++ lib/plugins/odp/event_api_manager/index.node.ts | 16 ++++++++++++++++ lib/plugins/odp_manager/index.browser.ts | 2 +- lib/utils/promise/resolvablePromise.ts | 16 ++++++++++++++++ tests/odpEventApiManager.spec.ts | 2 +- tests/odpEventManager.spec.ts | 2 +- tests/odpManager.browser.spec.ts | 2 +- tests/odpManager.spec.ts | 2 +- tests/odpSegmentManager.spec.ts | 2 +- tests/testUtils.ts | 2 +- 21 files changed, 66 insertions(+), 18 deletions(-) diff --git a/lib/core/odp/odp_event_api_manager.ts b/lib/core/odp/odp_event_api_manager.ts index 20067a178..35ffcc4e8 100644 --- a/lib/core/odp/odp_event_api_manager.ts +++ b/lib/core/odp/odp_event_api_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index d6b702de9..7d6bca519 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index f45608a1e..450d3cc52 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/core/odp/odp_segment_manager.ts b/lib/core/odp/odp_segment_manager.ts index b16df1754..501235f48 100644 --- a/lib/core/odp/odp_segment_manager.ts +++ b/lib/core/odp/odp_segment_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/core/project_config/index.tests.js b/lib/core/project_config/index.tests.js index 82c96b6d1..24134e3cd 100644 --- a/lib/core/project_config/index.tests.js +++ b/lib/core/project_config/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2023, Optimizely + * Copyright 2016-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/core/project_config/index.ts b/lib/core/project_config/index.ts index d2fa02bba..68ffbeacd 100644 --- a/lib/core/project_config/index.ts +++ b/lib/core/project_config/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2023, Optimizely + * Copyright 2016-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/core/project_config/project_config_manager.tests.js b/lib/core/project_config/project_config_manager.tests.js index e9b9322f5..b8fe8f8d3 100644 --- a/lib/core/project_config/project_config_manager.tests.js +++ b/lib/core/project_config/project_config_manager.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, 2022, Optimizely + * Copyright 2019-2020, 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/core/project_config/project_config_manager.ts b/lib/core/project_config/project_config_manager.ts index cd311a3cf..3f3aea4df 100644 --- a/lib/core/project_config/project_config_manager.ts +++ b/lib/core/project_config/project_config_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2022, Optimizely + * Copyright 2019-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/index.node.ts b/lib/index.node.ts index a78599814..50a7829b8 100644 --- a/lib/index.node.ts +++ b/lib/index.node.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2016-2017, 2019-2024 Optimizely, Inc. and contributors * + * Copyright 2016-2017, 2019-2024 Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index 1624df534..3f5b3e232 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2016-2023, Optimizely, Inc. and contributors * + * Copyright 2016-2024, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * diff --git a/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts index db6a96932..0b689237a 100644 --- a/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2020-2023, Optimizely, Inc. and contributors * + * Copyright 2020-2024, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * diff --git a/lib/plugins/odp/event_api_manager/index.browser.ts b/lib/plugins/odp/event_api_manager/index.browser.ts index 9f9a78ab8..8a21a462c 100644 --- a/lib/plugins/odp/event_api_manager/index.browser.ts +++ b/lib/plugins/odp/event_api_manager/index.browser.ts @@ -1,3 +1,19 @@ +/**************************************************************************** + * Copyright 2024, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; import { LogLevel } from '../../../modules/logging'; diff --git a/lib/plugins/odp/event_api_manager/index.node.ts b/lib/plugins/odp/event_api_manager/index.node.ts index aeaf57020..0b8b4e3ba 100644 --- a/lib/plugins/odp/event_api_manager/index.node.ts +++ b/lib/plugins/odp/event_api_manager/index.node.ts @@ -1,3 +1,19 @@ +/**************************************************************************** + * Copyright 2024, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + import { OdpConfig, OdpIntegrationConfig } from '../../../core/odp/odp_config'; import { OdpEvent } from '../../../core/odp/odp_event'; import { OdpEventApiManager } from '../../../core/odp/odp_event_api_manager'; diff --git a/lib/plugins/odp_manager/index.browser.ts b/lib/plugins/odp_manager/index.browser.ts index 8cee68955..e7095364a 100644 --- a/lib/plugins/odp_manager/index.browser.ts +++ b/lib/plugins/odp_manager/index.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/utils/promise/resolvablePromise.ts b/lib/utils/promise/resolvablePromise.ts index e5ec5f864..354df2b7d 100644 --- a/lib/utils/promise/resolvablePromise.ts +++ b/lib/utils/promise/resolvablePromise.ts @@ -1,3 +1,19 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + const noop = () => {}; export type ResolvablePromise = { diff --git a/tests/odpEventApiManager.spec.ts b/tests/odpEventApiManager.spec.ts index 2d06e9570..c989b76a6 100644 --- a/tests/odpEventApiManager.spec.ts +++ b/tests/odpEventApiManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/odpEventManager.spec.ts b/tests/odpEventManager.spec.ts index 35fbdcf44..31bc7a753 100644 --- a/tests/odpEventManager.spec.ts +++ b/tests/odpEventManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/odpManager.browser.spec.ts b/tests/odpManager.browser.spec.ts index 536fd7c53..b9ecb76f0 100644 --- a/tests/odpManager.browser.spec.ts +++ b/tests/odpManager.browser.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index 53941e7db..af0c13d98 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023, Optimizely + * Copyright 2023-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/odpSegmentManager.spec.ts b/tests/odpSegmentManager.spec.ts index 99d3c2f62..f4421175f 100644 --- a/tests/odpSegmentManager.spec.ts +++ b/tests/odpSegmentManager.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022-2023, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/tests/testUtils.ts b/tests/testUtils.ts index 3145502c9..981154932 100644 --- a/tests/testUtils.ts +++ b/tests/testUtils.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 8cbb767abf12e38f971d7c177eef53797de35fad Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Thu, 4 Apr 2024 17:59:27 +0600 Subject: [PATCH 21/24] remove console log --- lib/core/odp/odp_manager.ts | 13 ++++--------- lib/core/odp/odp_segment_manager.ts | 6 ------ tests/testUtils.ts | 1 - 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index 450d3cc52..af818026c 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -211,16 +211,13 @@ export abstract class OdpManager implements IOdpManager { * @returns {Promise} A promise holding either a list of qualified segments or null. */ async fetchQualifiedSegments(userId: string, options: Array = []): Promise { - console.log('fetch woot'); - if (!this.odpIntegrationConfig) { - console.log('wat no config '); - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); + if (!this.odpIntegrationConfig) { + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); return null; } if (!this.odpIntegrationConfig.integrated) { - console.log('wat no integration '); - this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); + this.logger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_INTEGRATED); return null; } @@ -228,9 +225,7 @@ export abstract class OdpManager implements IOdpManager { return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, userId, options); } - const foo = await this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); - console.log('foo is ', foo); - return foo; + return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); } /** diff --git a/lib/core/odp/odp_segment_manager.ts b/lib/core/odp/odp_segment_manager.ts index 501235f48..ac92f5e33 100644 --- a/lib/core/odp/odp_segment_manager.ts +++ b/lib/core/odp/odp_segment_manager.ts @@ -93,12 +93,6 @@ export class OdpSegmentManager implements IOdpSegmentManager { userValue: string, options: Array ): Promise { - // const { apiHost: odpApiHost, apiKey: odpApiKey } = this.odpConfig; - - // if (!odpApiKey || !odpApiHost) { - // this.logger.log(LogLevel.WARNING, ERROR_MESSAGES.FETCH_SEGMENTS_FAILED_INVALID_IDENTIFIER); - // return null; - // } if (!this.odpConfig) { this.logger.log(LogLevel.WARNING, ERROR_MESSAGES.ODP_CONFIG_NOT_AVAILABLE); return null; diff --git a/tests/testUtils.ts b/tests/testUtils.ts index 981154932..2af292e09 100644 --- a/tests/testUtils.ts +++ b/tests/testUtils.ts @@ -42,7 +42,6 @@ export const getTestPersistentCache = (): PersistentKeyValueCache => { }), set: jest.fn().mockImplementation((): Promise => { - console.log('mock set called'); return Promise.resolve(); }), From fe1f879010a9fcf3527904f4480e23add6b4e1b2 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Fri, 5 Apr 2024 00:34:19 +0600 Subject: [PATCH 22/24] fix review --- lib/core/odp/odp_config.ts | 2 +- lib/core/odp/odp_event_manager.ts | 6 +++--- lib/core/odp/odp_manager.ts | 4 ++-- tests/odpManager.spec.ts | 34 ------------------------------- 4 files changed, 6 insertions(+), 40 deletions(-) diff --git a/lib/core/odp/odp_config.ts b/lib/core/odp/odp_config.ts index f394a0868..4e4f41855 100644 --- a/lib/core/odp/odp_config.ts +++ b/lib/core/odp/odp_config.ts @@ -68,7 +68,7 @@ export type OdpIntegratedConfig = { readonly odpConfig: OdpConfig; } -export const odpIntegrationEquals = (config1: OdpIntegrationConfig, config2: OdpIntegrationConfig): boolean => { +export const odpIntegrationsAreEqual = (config1: OdpIntegrationConfig, config2: OdpIntegrationConfig): boolean => { if (config1.integrated !== config2.integrated) { return false; } diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index 7d6bca519..859937fe3 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -129,7 +129,7 @@ export abstract class OdpEventManager implements IOdpEventManager { */ private readonly userAgentParser?: IUserAgentParser; - private retires: number; + private retries: number; /** @@ -168,7 +168,7 @@ export abstract class OdpEventManager implements IOdpEventManager { this.initParams(batchSize, queueSize, flushInterval); this.status = Status.Stopped; this.userAgentParser = userAgentParser; - this.retires = retries || MAX_RETRIES; + this.retries = retries || MAX_RETRIES; if (userAgentParser) { const { os, device } = userAgentParser.parseUserAgentInfo(); @@ -398,7 +398,7 @@ export abstract class OdpEventManager implements IOdpEventManager { do { shouldRetry = await this.apiManager.sendEvents(odpConfig, batch); attemptNumber += 1; - } while (shouldRetry && attemptNumber < this.retires); + } while (shouldRetry && attemptNumber < this.retries); }); } } diff --git a/lib/core/odp/odp_manager.ts b/lib/core/odp/odp_manager.ts index af818026c..4da2f6191 100644 --- a/lib/core/odp/odp_manager.ts +++ b/lib/core/odp/odp_manager.ts @@ -20,7 +20,7 @@ import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums'; import { VuidManager } from '../../plugins/vuid_manager'; -import { OdpConfig, OdpIntegrationConfig, odpIntegrationEquals } from './odp_config'; +import { OdpConfig, OdpIntegrationConfig, odpIntegrationsAreEqual } from './odp_config'; import { IOdpEventManager } from './odp_event_manager'; import { IOdpSegmentManager } from './odp_segment_manager'; import { OptimizelySegmentOption } from './optimizely_segment_option'; @@ -183,7 +183,7 @@ export abstract class OdpManager implements IOdpManager { this.configPromise.resolve(); // do nothing if config did not change - if (this.odpIntegrationConfig && odpIntegrationEquals(this.odpIntegrationConfig, odpIntegrationConfig)) { + if (this.odpIntegrationConfig && odpIntegrationsAreEqual(this.odpIntegrationConfig, odpIntegrationConfig)) { return false; } diff --git a/tests/odpManager.spec.ts b/tests/odpManager.spec.ts index af0c13d98..90228cc52 100644 --- a/tests/odpManager.spec.ts +++ b/tests/odpManager.spec.ts @@ -125,40 +125,6 @@ describe('OdpManager', () => { resetCalls(mockSegmentManager); }); - // const odpManagerInstance = (config?: OdpConfig) => - // new OdpManager({ - // odpOptions: { - // eventManager, - // segmentManager, - // segmentsRequestHandler: defaultRequestHandler, - // eventRequestHandler: defaultRequestHandler, - // }, - // }); - - // it('should drop relevant calls when OdpManager is initialized with the disabled flag', async () => { - // const odpManager = new OdpManager({ - // logger, - // odpOptions: { - // disabled: true, - // segmentsRequestHandler: defaultRequestHandler, - // eventRequestHandler: defaultRequestHandler, - // }, - // }); - // verify(mockLogger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED)).once(); - - // odpManager.updateSettings(new OdpConfig('valid', 'host', 'pixel-url', [])); - // expect(odpManager.odpConfig).toBeUndefined; - - // await odpManager.fetchQualifiedSegments('user1', []); - // verify(mockLogger.log(LogLevel.ERROR, ERROR_MESSAGES.ODP_NOT_ENABLED)).once(); - - // odpManager.identifyUser('user1'); - // verify(mockLogger.log(LogLevel.DEBUG, LOG_MESSAGES.ODP_IDENTIFY_FAILED_ODP_DISABLED)).once(); - - // expect(odpManager.eventManager).toBeUndefined; - // expect(odpManager.segmentManager).toBeUndefined; - // }); - it('should be in stopped status and not ready if constructed without odpIntegrationConfig', () => { const odpManager = testOdpManager({ From 30b12949b6d096ec35178ec2de43e3baa277d37c Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Sat, 6 Apr 2024 01:41:27 +0600 Subject: [PATCH 23/24] undo clear timeout --- lib/core/odp/odp_event_manager.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index 859937fe3..c64776dc1 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -336,16 +336,18 @@ export abstract class OdpEventManager implements IOdpEventManager { if (this.status !== Status.Running) { return; } - - this.clearCurrentTimeout(); if (shouldFlush) { // clear the queue completely + this.clearCurrentTimeout(); + while (this.queueContainsItems()) { this.makeAndSend1Batch(); } } else if (this.queueHasBatches()) { // Check if queue has a full batch available + this.clearCurrentTimeout(); + while (this.queueHasBatches()) { this.makeAndSend1Batch(); } From 117a4b52d658f2cb089a888e97ca494d2a372ed0 Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Sat, 6 Apr 2024 03:15:39 +0600 Subject: [PATCH 24/24] remove unnecessary line --- lib/core/odp/odp_event_manager.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/core/odp/odp_event_manager.ts b/lib/core/odp/odp_event_manager.ts index c64776dc1..80baa4822 100644 --- a/lib/core/odp/odp_event_manager.ts +++ b/lib/core/odp/odp_event_manager.ts @@ -305,7 +305,6 @@ export abstract class OdpEventManager implements IOdpEventManager { this.logger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.'); return; } - const hasNecessaryIdentifiers = this.hasNecessaryIdentifiers; if (!this.hasNecessaryIdentifiers(event)) { this.logger.log(LogLevel.ERROR, 'ODP events should have at least one key-value pair in identifiers.'); @@ -347,7 +346,7 @@ export abstract class OdpEventManager implements IOdpEventManager { } else if (this.queueHasBatches()) { // Check if queue has a full batch available this.clearCurrentTimeout(); - + while (this.queueHasBatches()) { this.makeAndSend1Batch(); }