Skip to content

fix(ats): [FSSDK-9023] Fix ODP Exports #812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 14 additions & 41 deletions packages/optimizely-sdk/lib/core/odp/odp_event_api_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@

import { LogHandler, LogLevel } from '../../modules/logging';
import { OdpEvent } from './odp_event';
import { isBrowserContext } from './odp_utils';
import { RequestHandler } from '../../utils/http_request_handler/http';
import { ODP_EVENT_BROWSER_ENDPOINT } from '../../utils/enums';

const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed';

Expand All @@ -32,10 +30,9 @@ export interface IOdpEventApiManager {
/**
* Concrete implementation for accessing the ODP REST API
*/
export class OdpEventApiManager implements IOdpEventApiManager {
export abstract class OdpEventApiManager implements IOdpEventApiManager {
private readonly logger: LogHandler;
private readonly requestHandler: RequestHandler;
private readonly isBrowser: boolean;

/**
* Creates instance to access Optimizely Data Platform (ODP) REST API
Expand All @@ -45,7 +42,10 @@ export class OdpEventApiManager implements IOdpEventApiManager {
constructor(requestHandler: RequestHandler, logger: LogHandler) {
this.requestHandler = requestHandler;
this.logger = logger;
this.isBrowser = isBrowserContext()
}

public getLogger(): LogHandler {
return this.logger;
}

/**
Expand All @@ -68,39 +68,11 @@ export class OdpEventApiManager implements IOdpEventApiManager {
return shouldRetry;
}

if (events.length > 1 && this.isBrowser) {
this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (browser only supports batch size 1)`);
if (!this.shouldSendEvents(events)) {
return shouldRetry;
}

let method, endpoint, headers, data;

if (this.isBrowser) {
method = 'GET';
const event = events[0];
const url = new URL(ODP_EVENT_BROWSER_ENDPOINT);
event.identifiers.forEach((v, k) =>{
url.searchParams.append(k, v);
});
event.data.forEach((v, k) =>{
url.searchParams.append(k, v as string);
});
url.searchParams.append('tracker_id', apiKey);
url.searchParams.append('event_type', event.type);
url.searchParams.append('vdl_action', event.action);
endpoint = url.toString();
headers = {};
} else {
method = 'POST';
endpoint = `${apiHost}/v3/events`;
headers = {
'Content-Type': 'application/json',
'x-api-key': apiKey,
};
data = JSON.stringify(events, this.replacer);
}


const { method, endpoint, headers, data } = this.generateRequestData(apiHost, apiKey, events);

let statusCode = 0;
try {
Expand All @@ -127,11 +99,12 @@ export class OdpEventApiManager implements IOdpEventApiManager {
return shouldRetry;
}

private replacer(_: unknown, value: unknown) {
if (value instanceof Map) {
return Object.fromEntries(value);
} else {
return value;
}
protected abstract shouldSendEvents(events: OdpEvent[]): boolean;

protected abstract generateRequestData(apiHost: string, apiKey: string, events: OdpEvent[]): {
method: string,
endpoint: string,
headers: {[key: string]: string},
data: string,
}
}
68 changes: 24 additions & 44 deletions packages/optimizely-sdk/lib/core/odp/odp_event_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { ERROR_MESSAGES, ODP_USER_KEY, ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION
import { OdpEvent } from './odp_event';
import { OdpConfig } from './odp_config';
import { OdpEventApiManager } from './odp_event_api_manager';
import { invalidOdpDataFound, isBrowserContext } from './odp_utils';
import { invalidOdpDataFound } from './odp_utils';

const MAX_RETRIES = 3;
const DEFAULT_BATCH_SIZE = 10;
Expand Down Expand Up @@ -61,16 +61,16 @@ export interface IOdpEventManager {
/**
* Concrete implementation of a manager for persisting events to the Optimizely Data Platform
*/
export class OdpEventManager implements IOdpEventManager {
export abstract class OdpEventManager implements IOdpEventManager {
/**
* Current state of the event processor
*/
public state: STATE = STATE.STOPPED;
/**
* Queue for holding all events to be eventually dispatched
* @private
* @protected
*/
private queue = new Array<OdpEvent>();
protected queue = new Array<OdpEvent>();
/**
* Identifier of the currently running timeout so clearCurrentTimeout() can be called
* @private
Expand All @@ -93,19 +93,19 @@ export class OdpEventManager implements IOdpEventManager {
private readonly logger: LogHandler;
/**
* Maximum queue size
* @private
* @protected
*/
private readonly queueSize: number;
protected queueSize!: number;
/**
* Maximum number of events to process at once. Ignored in browser context
* @private
* @protected
*/
private readonly batchSize: number;
protected batchSize!: number;
/**
* Milliseconds between setTimeout() to process new batches. Ignored in browser context
* @private
* @protected
*/
private readonly flushInterval: number;
protected flushInterval!: number;
/**
* Type of execution context eg node, js, react
* @private
Expand Down Expand Up @@ -141,33 +141,16 @@ export class OdpEventManager implements IOdpEventManager {
this.logger = logger;
this.clientEngine = clientEngine;
this.clientVersion = clientVersion;

const isBrowser = isBrowserContext();

const defaultQueueSize = isBrowser ? DEFAULT_BROWSER_QUEUE_SIZE : DEFAULT_SERVER_QUEUE_SIZE;
this.queueSize = queueSize || defaultQueueSize;
this.batchSize = batchSize || DEFAULT_BATCH_SIZE;

if (flushInterval === 0 || isBrowser) {
// disable event batching
this.batchSize = 1;
this.flushInterval = 0;
} else {
this.flushInterval = flushInterval || DEFAULT_FLUSH_INTERVAL_MSECS;
}

if (isBrowser) {
if (typeof batchSize !== 'undefined' && batchSize !== 1) {
this.logger.log(LogLevel.WARNING, 'ODP event batch size must be 1 in the browser.');
}
if (typeof flushInterval !== 'undefined' && flushInterval !== 0) {
this.logger.log(LogLevel.WARNING, 'ODP event flush interval must be 0 in the browser.');
}
}

this.initParams(batchSize, queueSize, flushInterval);
this.state = STATE.STOPPED;
}

protected abstract initParams(
batchSize: number | undefined,
queueSize: number | undefined,
flushInterval: number | undefined,
): void;

/**
* Update ODP configuration settings.
* @param newConfig New configuration to apply
Expand Down Expand Up @@ -401,19 +384,12 @@ export class OdpEventManager implements IOdpEventManager {
if (this.odpConfig.isReady()) {
return true;
}

if (!isBrowserContext()) {
// if Node/server-side context, empty queue items before ready state
this.logger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.');
this.queue = new Array<OdpEvent>();
} else {
// in Browser/client-side context, give debug message but leave events in queue
this.logger.log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.');
}

this.discardEventsIfNeeded();
return false;
}

protected abstract discardEventsIfNeeded(): void;

/**
* Add additional common data including an idempotent ID and execution context to event data
* @param sourceData Existing event data to augment
Expand All @@ -430,4 +406,8 @@ export class OdpEventManager implements IOdpEventManager {
sourceData.forEach((value, key) => data.set(key, value));
return data;
}

protected getLogger(): LogHandler {
return this.logger;
}
}
88 changes: 10 additions & 78 deletions packages/optimizely-sdk/lib/core/odp/odp_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,26 @@
* limitations under the License.
*/

import { BROWSER_CLIENT_VERSION, LOG_MESSAGES } from './../../utils/enums/index';
import { LOG_MESSAGES } from './../../utils/enums/index';
import { getLogger, LogHandler, LogLevel } from '../../modules/logging';
import { ERROR_MESSAGES, ODP_USER_KEY } from '../../utils/enums';

import { RequestHandler } from './../../utils/http_request_handler/http';

import { LRUCache } from './../../utils/lru_cache/lru_cache';

import { VuidManager } from '../../plugins/vuid_manager';

import { OdpConfig } from './odp_config';
import { OdpEventManager } from './odp_event_manager';
import { OdpSegmentManager } from './odp_segment_manager';
import { OdpSegmentApiManager } from './odp_segment_api_manager';
import { OdpEventApiManager } from './odp_event_api_manager';
import { OptimizelySegmentOption } from './optimizely_segment_option';
import { invalidOdpDataFound } from './odp_utils';
import { OdpEvent } from './odp_event';
import { OdpOptions } from '../../shared_types';

/**
* @param {LRUCache<string, string>[]} segmentLRUCache Cache to be used for storing segments.
* @param {RequestHandler} segmentRequestHandler HTTP request handler that will be used by the ODP Segment Manager.
* @param {RequestHandler} eventRequestHandler HTTP request handler that will be used by the ODP Event Manager.
* @param {LogHandler} logger (Optional) Accepts custom LogHandler. Defaults to the default global LogHandler.
* @param {string} clientEngine (Optional) String denoting specific client engine being used. Defaults to 'javascript-sdk'.
* @param {string} clientVersion (Optional) String denoting specific client version. Defaults to current version value from package.json.
* @param {OdpOptions} odpOptions (Optional) Configuration settings for various ODP options from segment cache size to event flush interval.
*/
interface OdpManagerConfig {
segmentLRUCache: LRUCache<string, string[]>;
segmentRequestHandler: RequestHandler;
eventRequestHandler: RequestHandler;
logger?: LogHandler;
clientEngine?: string;
clientVersion?: string;
odpOptions?: OdpOptions;
}

/**
* Orchestrates segments manager, event manager, and ODP configuration
*/
export class OdpManager {
enabled: boolean;
logger: LogHandler;
export abstract class OdpManager {
initPromise?: Promise<void>;
enabled = true;
logger: LogHandler = getLogger();
odpConfig: OdpConfig = new OdpConfig();

/**
Expand All @@ -73,54 +48,7 @@ export class OdpManager {
*/
public eventManager: OdpEventManager | undefined;

constructor({
segmentLRUCache,
segmentRequestHandler,
eventRequestHandler,
logger,
clientEngine,
clientVersion,
odpOptions,
}: OdpManagerConfig) {
this.enabled = !odpOptions?.disabled;
this.logger = logger || getLogger();

if (!this.enabled) {
this.logger.log(LogLevel.INFO, LOG_MESSAGES.ODP_DISABLED);
return;
}

// 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,
segmentLRUCache,
new OdpSegmentApiManager(segmentRequestHandler, this.logger)
);
}

// Set up Events Manager (Events REST API Interface)
if (odpOptions?.eventManager) {
this.eventManager = odpOptions.eventManager;
this.eventManager.updateSettings(this.odpConfig);
} else {
this.eventManager = new OdpEventManager({
odpConfig: this.odpConfig,
apiManager: new OdpEventApiManager(eventRequestHandler, this.logger),
logger: this.logger,
clientEngine: clientEngine || 'javascript-sdk',
clientVersion: clientVersion || BROWSER_CLIENT_VERSION,
flushInterval: odpOptions?.eventFlushInterval,
batchSize: odpOptions?.eventBatchSize,
queueSize: odpOptions?.eventQueueSize,
});
}

this.eventManager.start();
}
constructor() {}

/**
* Provides a method to update ODP Manager's ODP Config API Key, API Host, and Audience Segments
Expand Down Expand Up @@ -246,4 +174,8 @@ export class OdpManager {

this.eventManager.sendEvent(new OdpEvent(type, action, identifiers, data));
}

public abstract isVuidEnabled(): boolean;

public abstract getVuid(): string | undefined;
}
11 changes: 0 additions & 11 deletions packages/optimizely-sdk/lib/core/odp/odp_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,3 @@ export function invalidOdpDataFound(data: Map<string, any>): boolean {
});
return foundInvalidValue;
}

/**
* Determine if the runtime environment is a browser
* @returns True if in the browser
* @private
*/
export function isBrowserContext(): boolean {
return !(typeof process !== "undefined" &&
process.versions != null &&
process.versions.node != null);
}
6 changes: 6 additions & 0 deletions packages/optimizely-sdk/lib/export_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* This file contains a collection of all types to be externally exported.
*/

export {
UserAttributes,
OptimizelyConfig,
Expand All @@ -38,4 +43,5 @@ export {
ActivateListenerPayload,
TrackListenerPayload,
NotificationCenter,
OptimizelySegmentOption,
} from './shared_types';
Loading