From 7b0291758b327a33dc3c092088a3082a39a17f05 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 22 Sep 2020 23:21:45 +0500 Subject: [PATCH 01/22] added declaration file for event builder --- .../core/condition_tree_evaluator/index.ts | 3 +- .../lib/core/event_builder/index.d.ts | 99 ++++++++++++++++ .../lib/core/project_config/entities.ts | 24 ++++ .../lib/core/project_config/index.d.ts | 109 ++++++++++++++++++ 4 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 packages/optimizely-sdk/lib/core/event_builder/index.d.ts create mode 100644 packages/optimizely-sdk/lib/core/project_config/entities.ts create mode 100644 packages/optimizely-sdk/lib/core/project_config/index.d.ts diff --git a/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts b/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts index 855372d1b..030afbfa7 100644 --- a/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts +++ b/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts @@ -19,8 +19,7 @@ const OR_CONDITION = 'or'; const NOT_CONDITION = 'not'; const DEFAULT_OPERATOR_TYPES = [AND_CONDITION, OR_CONDITION, NOT_CONDITION]; - -type ConditionTree = Leaf | unknown[]; +export type ConditionTree = Leaf | unknown[]; type LeafEvaluator = (leaf: Leaf) => boolean | null; diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts new file mode 100644 index 000000000..42d78f83f --- /dev/null +++ b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts @@ -0,0 +1,99 @@ +/** + * Copyright 2020, 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 + * + * 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. + */ + +declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { + import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; + import { LogHandler } from '@optimizely/js-sdk-logging'; + import { EventTags, UserAttributes } from '@optimizely/optimizely-sdk'; + import { Event as EventLoggingEndpoint } from '@optimizely/optimizely-sdk'; + + interface ImpressionOptions { + attributes: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + experimentId: string; + eventKey: string; + variationId: string; + logger: LogHandler; + userId: string; + } + + interface ImpressionConfig { + experimentKey: string; + variationKey: string; + userId: string; + userAttributes: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface ImpressionEvent {} + + interface ConversionEventOptions { + attributes: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + eventKey: string; + logger: LogHandler; + userId: string; + eventTags: EventTags; + } + + interface ConversionConfig { + eventKey: string; + eventTags: EventTags; + userId: string; + userAttributes: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface ConversionEvent {} + + export interface EventBuilder { + /** + * Create impression event params to be sent to the logging endpoint + * @param {ImpressionOptions} options Object containing values needed to build impression event + * @return {EventLoggingEndpoint} Params to be used in impression event logging endpoint call + */ + getImpressionEvent(options: ImpressionOptions): EventLoggingEndpoint; + /** + * Create conversion event params to be sent to the logging endpoint + * @param {ConversionEventOptions} options Object containing values needed to build conversion event + * @return {EventLoggingEndpoint} Params to be used in conversion event logging endpoint call + */ + getConversionEvent(options: ConversionEventOptions): EventLoggingEndpoint; + + /** + * Creates an ImpressionEvent object from decision data + * @param {ImpressionConfig} config + * @return {ImpressionEvent} an ImpressionEvent object + */ + buildImpressionEvent(config: ImpressionConfig): ImpressionEvent; + /** + * Creates a ConversionEvent object from track + * @param {ConversionConfig} config + * @return {ConversionEvent} a ConversionEvent object + */ + buildConversionEvent(config: ConversionConfig): ConversionEvent; + } +} diff --git a/packages/optimizely-sdk/lib/core/project_config/entities.ts b/packages/optimizely-sdk/lib/core/project_config/entities.ts new file mode 100644 index 000000000..71d3fab1c --- /dev/null +++ b/packages/optimizely-sdk/lib/core/project_config/entities.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2020, 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 + * + * 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. + */ + +export interface FeatureVariable { + type: string; +} + +export interface FeatureFlag { + variables: FeatureVariable[]; +} + diff --git a/packages/optimizely-sdk/lib/core/project_config/index.d.ts b/packages/optimizely-sdk/lib/core/project_config/index.d.ts new file mode 100644 index 000000000..c92f06b25 --- /dev/null +++ b/packages/optimizely-sdk/lib/core/project_config/index.d.ts @@ -0,0 +1,109 @@ +/** + * Copyright 2020, 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 + * + * 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. + */ + +declare module '@optimizely/optimizely-sdk/lib/core/project_config' { + import { LogHandler } from '@optimizely/js-sdk-logging'; + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface ProjectConfig {} + /** + * Determine for given experiment if event is running, which determines whether should be dispatched or not + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Experiment key for which the status is to be determined + * @return {boolean} True is the experiment is running + * False is the experiment is not running + * + */ + export function isRunning(configObj: ProjectConfig, experimentKey: string): boolean; + + /** + * Get the variation ID given the experiment key and variation key + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Key of the experiment the variation belongs to + * @param {string} variationKey The variation key + * @return {string} the variation ID + */ + export function getVariationIdFromExperimentAndVariationKey(configObj: ProjectConfig, experimentKey: string, variationKey: string): string; + + /** + * Get experiment ID for the provided experiment key + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Experiment key for which ID is to be determined + * @return {string} Experiment ID corresponding to the provided experiment key + * @throws If experiment key is not in datafile + */ + export function getExperimentId(configObj: ProjectConfig, experimentKey: string): string | never; + + /** + * Check if the event with a specific key is present in the datafile + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} eventKey Event key for which event is to be determined + * @returns {boolean} True if key exists in the datafile + * False if key does not exist in the datafile + */ + export function eventWithKeyExists(configObj: ProjectConfig, eventKey: string): boolean; + + /** + * Check if the experiment is belongs to any feature + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentId Experiment ID of an experiment + * @returns {boolean} True if experiement belongs to any feature + * False if experiement does not belong to any feature + */ + export function isFeatureExperiment(configObj: ProjectConfig, experimentId: string): boolean; + + /** + * Get feature from provided feature key. Log an error if no feature exists in + * the project config with the given key. + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} featureKey Key of a feature for which feature is to be determined + * @param {LogHandler} logger Logger instance + * @return {FeatureFlag|null} Feature object, or null if no feature with the given + * key exists + */ + export function getFeatureFromKey(configObj: ProjectConfig, featureKey: string, logger: LogHandler): import('./entities').FeatureFlag | null; + + /** + * Get the variable with the given key associated with the feature with the + * given key. If the feature key or the variable key are invalid, log an error + * message. + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} featureKey Key of a feature for which feature is to be determined + * @param {string} variableKey Key of a variable for which variable is to be determined + * @param {LogHandler} logger Logger instances + * @return {FeatureVariable|null} Variable object, or null one or both of the given + * feature and variable keys are invalid + */ + export function getVariableForFeature(configObj: ProjectConfig, featureKey: string, variableKey: string, logger: LogHandler): import('./entities').FeatureVariable | null; + + /** + * Given a variable value in string form, try to cast it to the argument type. + * If the type cast succeeds, return the type casted value, otherwise log an + * error and return null. + * @param {string} variableValue Variable value in string form + * @param {string} type Type of the variable whose value was passed + * in the first argument. Must be one of + * FEATURE_VARIABLE_TYPES in + * lib/utils/enums/index.js. The return value's + * type is determined by this argument (boolean + * for BOOLEAN, number for INTEGER or DOUBLE, + * and string for STRING). + * @param {LogHandler} logger Logger instance + * @returns {T} Variable value of the appropriate type, or + * null if the type cast failed + */ + export function getTypeCastValue(variableValue: string, type: string, logger: LogHandler): T; +} From 3920a6668243fd13f653e95b330914d3d82d8681 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 22 Sep 2020 23:34:30 +0500 Subject: [PATCH 02/22] added declaration file for notification center --- .../core/condition_tree_evaluator/index.ts | 3 +- .../lib/core/notification_center/index.d.ts | 93 +++++++++++++++ .../lib/core/project_config/entities.ts | 24 ++++ .../lib/core/project_config/index.d.ts | 109 ++++++++++++++++++ packages/optimizely-sdk/lib/index.d.ts | 4 +- 5 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 packages/optimizely-sdk/lib/core/notification_center/index.d.ts create mode 100644 packages/optimizely-sdk/lib/core/project_config/entities.ts create mode 100644 packages/optimizely-sdk/lib/core/project_config/index.d.ts diff --git a/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts b/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts index 855372d1b..030afbfa7 100644 --- a/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts +++ b/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts @@ -19,8 +19,7 @@ const OR_CONDITION = 'or'; const NOT_CONDITION = 'not'; const DEFAULT_OPERATOR_TYPES = [AND_CONDITION, OR_CONDITION, NOT_CONDITION]; - -type ConditionTree = Leaf | unknown[]; +export type ConditionTree = Leaf | unknown[]; type LeafEvaluator = (leaf: Leaf) => boolean | null; diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.d.ts b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts new file mode 100644 index 000000000..a5876f416 --- /dev/null +++ b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts @@ -0,0 +1,93 @@ +/* eslint-disable no-shadow */ +/** + * Copyright 2020, 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 + * + * 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 { UserAttributes, Experiment, Variation } from '@optimizely/optimizely-sdk'; +import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; + +declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { + export enum NOTIFICATION_TYPES { + ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', + DECISION = 'DECISION:type, userId, attributes, decisionInfo', + LOG_EVENT = 'LOG_EVENT:logEvent', + OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE', + TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event', + } + + export enum DECISION_NOTIFICATION_TYPES { + AB_TEST = 'ab-test', + FEATURE = 'feature', + FEATURE_TEST = 'feature-test', + FEATURE_VARIABLE = 'feature-variable', + ALL_FEATURE_VARIABLES = 'all-feature-variables', + } + + export type Options = { + logger: LogHandler; + errorHandle: ErrorHandler; + } + + export type SourceInfo = { + experimentKey?: string; + variationKey?: string; + } + + export type VariableValues = { + [name: string]: unknown; + }; + + export type DecisionInfo = { + experimentKey?: string; + variationKey?: string; + featureKey?: string; + featureEnabled?: boolean; + source?: string; + sourceInfo?: SourceInfo; + variableKey?: string; + variableValue?: unknown; + variableValues?: VariableValues; + variableType?: string; + } + + export interface NotificationData { + type?: DECISION_NOTIFICATION_TYPES; + userId?: string; + attributes?: UserAttributes; + decisionInfo?: DecisionInfo; + experiment?: Experiment; + variation?: Variation; + logEvent?: string; + eventKey?: string; + eventTags?: string; + } + + export interface NotificationCenter { + /** + * Fires notifications for the argument type. All registered callbacks for this type will be + * called. The notificationData object will be passed on to callbacks called. + * @param {NOTIFICATION_TYPES} notificationType One of NOTIFICATION_TYPES + * @param {NotificationData} notificationData Will be passed to callbacks called + */ + sendNotifications(notificationType: NOTIFICATION_TYPES, notificationData: NotificationData): void; + } + + /** + * Create an instance of NotificationCenter + * @param {Options} options + * @returns {NotificationCenter} An instance of NotificationCenter + */ + export function createNotificationCenter(options: Options): NotificationCenter; +} diff --git a/packages/optimizely-sdk/lib/core/project_config/entities.ts b/packages/optimizely-sdk/lib/core/project_config/entities.ts new file mode 100644 index 000000000..71d3fab1c --- /dev/null +++ b/packages/optimizely-sdk/lib/core/project_config/entities.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2020, 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 + * + * 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. + */ + +export interface FeatureVariable { + type: string; +} + +export interface FeatureFlag { + variables: FeatureVariable[]; +} + diff --git a/packages/optimizely-sdk/lib/core/project_config/index.d.ts b/packages/optimizely-sdk/lib/core/project_config/index.d.ts new file mode 100644 index 000000000..c92f06b25 --- /dev/null +++ b/packages/optimizely-sdk/lib/core/project_config/index.d.ts @@ -0,0 +1,109 @@ +/** + * Copyright 2020, 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 + * + * 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. + */ + +declare module '@optimizely/optimizely-sdk/lib/core/project_config' { + import { LogHandler } from '@optimizely/js-sdk-logging'; + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface ProjectConfig {} + /** + * Determine for given experiment if event is running, which determines whether should be dispatched or not + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Experiment key for which the status is to be determined + * @return {boolean} True is the experiment is running + * False is the experiment is not running + * + */ + export function isRunning(configObj: ProjectConfig, experimentKey: string): boolean; + + /** + * Get the variation ID given the experiment key and variation key + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Key of the experiment the variation belongs to + * @param {string} variationKey The variation key + * @return {string} the variation ID + */ + export function getVariationIdFromExperimentAndVariationKey(configObj: ProjectConfig, experimentKey: string, variationKey: string): string; + + /** + * Get experiment ID for the provided experiment key + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Experiment key for which ID is to be determined + * @return {string} Experiment ID corresponding to the provided experiment key + * @throws If experiment key is not in datafile + */ + export function getExperimentId(configObj: ProjectConfig, experimentKey: string): string | never; + + /** + * Check if the event with a specific key is present in the datafile + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} eventKey Event key for which event is to be determined + * @returns {boolean} True if key exists in the datafile + * False if key does not exist in the datafile + */ + export function eventWithKeyExists(configObj: ProjectConfig, eventKey: string): boolean; + + /** + * Check if the experiment is belongs to any feature + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentId Experiment ID of an experiment + * @returns {boolean} True if experiement belongs to any feature + * False if experiement does not belong to any feature + */ + export function isFeatureExperiment(configObj: ProjectConfig, experimentId: string): boolean; + + /** + * Get feature from provided feature key. Log an error if no feature exists in + * the project config with the given key. + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} featureKey Key of a feature for which feature is to be determined + * @param {LogHandler} logger Logger instance + * @return {FeatureFlag|null} Feature object, or null if no feature with the given + * key exists + */ + export function getFeatureFromKey(configObj: ProjectConfig, featureKey: string, logger: LogHandler): import('./entities').FeatureFlag | null; + + /** + * Get the variable with the given key associated with the feature with the + * given key. If the feature key or the variable key are invalid, log an error + * message. + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} featureKey Key of a feature for which feature is to be determined + * @param {string} variableKey Key of a variable for which variable is to be determined + * @param {LogHandler} logger Logger instances + * @return {FeatureVariable|null} Variable object, or null one or both of the given + * feature and variable keys are invalid + */ + export function getVariableForFeature(configObj: ProjectConfig, featureKey: string, variableKey: string, logger: LogHandler): import('./entities').FeatureVariable | null; + + /** + * Given a variable value in string form, try to cast it to the argument type. + * If the type cast succeeds, return the type casted value, otherwise log an + * error and return null. + * @param {string} variableValue Variable value in string form + * @param {string} type Type of the variable whose value was passed + * in the first argument. Must be one of + * FEATURE_VARIABLE_TYPES in + * lib/utils/enums/index.js. The return value's + * type is determined by this argument (boolean + * for BOOLEAN, number for INTEGER or DOUBLE, + * and string for STRING). + * @param {LogHandler} logger Logger instance + * @returns {T} Variable value of the appropriate type, or + * null if the type cast failed + */ + export function getTypeCastValue(variableValue: string, type: string, logger: LogHandler): T; +} diff --git a/packages/optimizely-sdk/lib/index.d.ts b/packages/optimizely-sdk/lib/index.d.ts index 3f110095f..040fc2bf0 100644 --- a/packages/optimizely-sdk/lib/index.d.ts +++ b/packages/optimizely-sdk/lib/index.d.ts @@ -180,7 +180,7 @@ declare module '@optimizely/optimizely-sdk' { logEvent: Event; } - interface Experiment { + export interface Experiment { id: string; key: string; status: string; @@ -196,7 +196,7 @@ declare module '@optimizely/optimizely-sdk' { forcedVariations: object; } - interface Variation { + export interface Variation { id: string; key: string; } From d3db6d373479f1a675a9402f8f1bd7e9877a1467 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 23 Sep 2020 14:37:57 +0500 Subject: [PATCH 03/22] comments addressed: imports moved inside module and errorHandler corrected and linting fixed --- .../lib/core/notification_center/index.d.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.d.ts b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts index a5876f416..b756c78fd 100644 --- a/packages/optimizely-sdk/lib/core/notification_center/index.d.ts +++ b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts @@ -15,10 +15,10 @@ * limitations under the License. */ -import { UserAttributes, Experiment, Variation } from '@optimizely/optimizely-sdk'; -import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; - declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { + import { UserAttributes, Experiment, Variation } from '@optimizely/optimizely-sdk'; + import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; + export enum NOTIFICATION_TYPES { ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', DECISION = 'DECISION:type, userId, attributes, decisionInfo', @@ -35,21 +35,21 @@ declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { ALL_FEATURE_VARIABLES = 'all-feature-variables', } - export type Options = { + export type Options = { logger: LogHandler; - errorHandle: ErrorHandler; - } + errorHandler: ErrorHandler; + }; export type SourceInfo = { experimentKey?: string; variationKey?: string; - } + }; export type VariableValues = { [name: string]: unknown; }; - export type DecisionInfo = { + export type DecisionInfo = { experimentKey?: string; variationKey?: string; featureKey?: string; @@ -60,7 +60,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { variableValue?: unknown; variableValues?: VariableValues; variableType?: string; - } + }; export interface NotificationData { type?: DECISION_NOTIFICATION_TYPES; From 143379ef77f9718e99c6756edd7eb67c6422d27b Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 23 Sep 2020 14:46:30 +0500 Subject: [PATCH 04/22] comment addressed: seperate declaration file for event_helper event_helper.d.ts --- .../lib/core/event_builder/event_helper.d.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 packages/optimizely-sdk/lib/core/event_builder/event_helper.d.ts diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helper.d.ts b/packages/optimizely-sdk/lib/core/event_builder/event_helper.d.ts new file mode 100644 index 000000000..f00721159 --- /dev/null +++ b/packages/optimizely-sdk/lib/core/event_builder/event_helper.d.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2020, 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 + * + * 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. + */ + +declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { + import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; + import { EventTags, UserAttributes } from '@optimizely/optimizely-sdk'; + + interface ImpressionConfig { + experimentKey: string; + variationKey: string; + userId: string; + userAttributes: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface ImpressionEvent {} + + interface ConversionConfig { + eventKey: string; + eventTags: EventTags; + userId: string; + userAttributes: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface ConversionEvent {} + + export interface EventHelper { + /** + * Creates an ImpressionEvent object from decision data + * @param {ImpressionConfig} config + * @return {ImpressionEvent} an ImpressionEvent object + */ + buildImpressionEvent(config: ImpressionConfig): ImpressionEvent; + /** + * Creates a ConversionEvent object from track + * @param {ConversionConfig} config + * @return {ConversionEvent} a ConversionEvent object + */ + buildConversionEvent(config: ConversionConfig): ConversionEvent; + } + } + \ No newline at end of file From 51ad50b8b9c073216b04d2c1d02ff35fefada124 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 23 Sep 2020 14:54:42 +0500 Subject: [PATCH 05/22] event_helpers module name and declaration file name corrected --- .../event_builder/{event_helper.d.ts => event_helpers.d.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/optimizely-sdk/lib/core/event_builder/{event_helper.d.ts => event_helpers.d.ts} (98%) diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helper.d.ts b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts similarity index 98% rename from packages/optimizely-sdk/lib/core/event_builder/event_helper.d.ts rename to packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts index f00721159..30ba89654 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/event_helper.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts @@ -44,7 +44,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { // eslint-disable-next-line @typescript-eslint/no-empty-interface interface ConversionEvent {} - export interface EventHelper { + export interface EventHelpers { /** * Creates an ImpressionEvent object from decision data * @param {ImpressionConfig} config From e9d9ce6946880a8e8dc0b653ae7018174e8062c1 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 23 Sep 2020 15:01:00 +0500 Subject: [PATCH 06/22] event helper functions and interfaces removed from event builder module --- .../lib/core/event_builder/index.d.ts | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts index 42d78f83f..c9cc7c30c 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts @@ -32,19 +32,6 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { userId: string; } - interface ImpressionConfig { - experimentKey: string; - variationKey: string; - userId: string; - userAttributes: UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; - } - - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface ImpressionEvent {} - interface ConversionEventOptions { attributes: UserAttributes; clientEngine: string; @@ -56,19 +43,6 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { eventTags: EventTags; } - interface ConversionConfig { - eventKey: string; - eventTags: EventTags; - userId: string; - userAttributes: UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; - } - - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface ConversionEvent {} - export interface EventBuilder { /** * Create impression event params to be sent to the logging endpoint @@ -82,18 +56,5 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { * @return {EventLoggingEndpoint} Params to be used in conversion event logging endpoint call */ getConversionEvent(options: ConversionEventOptions): EventLoggingEndpoint; - - /** - * Creates an ImpressionEvent object from decision data - * @param {ImpressionConfig} config - * @return {ImpressionEvent} an ImpressionEvent object - */ - buildImpressionEvent(config: ImpressionConfig): ImpressionEvent; - /** - * Creates a ConversionEvent object from track - * @param {ConversionConfig} config - * @return {ConversionEvent} a ConversionEvent object - */ - buildConversionEvent(config: ConversionConfig): ConversionEvent; } } From 5ed0fe569222c143cb32cebe4c2bdce0df37d203 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 23 Sep 2020 15:03:09 +0500 Subject: [PATCH 07/22] EOL in event_helpers.d.ts --- .../optimizely-sdk/lib/core/event_builder/event_helpers.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts index 30ba89654..b7d42e49b 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts @@ -59,4 +59,5 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { buildConversionEvent(config: ConversionConfig): ConversionEvent; } } + \ No newline at end of file From ade1028b7b2bfbfa770892fea0e7e1e2810ce5b2 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Wed, 23 Sep 2020 15:05:35 +0500 Subject: [PATCH 08/22] event_helper.d.ts linting fixed --- .../lib/core/event_builder/event_helpers.d.ts | 88 +++++++++---------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts index b7d42e49b..9333ac337 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts @@ -15,49 +15,47 @@ */ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { - import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; - import { EventTags, UserAttributes } from '@optimizely/optimizely-sdk'; - - interface ImpressionConfig { - experimentKey: string; - variationKey: string; - userId: string; - userAttributes: UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; - } - - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface ImpressionEvent {} - - interface ConversionConfig { - eventKey: string; - eventTags: EventTags; - userId: string; - userAttributes: UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; - } - - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface ConversionEvent {} - - export interface EventHelpers { - /** - * Creates an ImpressionEvent object from decision data - * @param {ImpressionConfig} config - * @return {ImpressionEvent} an ImpressionEvent object - */ - buildImpressionEvent(config: ImpressionConfig): ImpressionEvent; - /** - * Creates a ConversionEvent object from track - * @param {ConversionConfig} config - * @return {ConversionEvent} a ConversionEvent object - */ - buildConversionEvent(config: ConversionConfig): ConversionEvent; - } + import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; + import { EventTags, UserAttributes } from '@optimizely/optimizely-sdk'; + + interface ImpressionConfig { + experimentKey: string; + variationKey: string; + userId: string; + userAttributes: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface ImpressionEvent {} + + interface ConversionConfig { + eventKey: string; + eventTags: EventTags; + userId: string; + userAttributes: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + interface ConversionEvent {} + + export interface EventHelpers { + /** + * Creates an ImpressionEvent object from decision data + * @param {ImpressionConfig} config + * @return {ImpressionEvent} an ImpressionEvent object + */ + buildImpressionEvent(config: ImpressionConfig): ImpressionEvent; + /** + * Creates a ConversionEvent object from track + * @param {ConversionConfig} config + * @return {ConversionEvent} a ConversionEvent object + */ + buildConversionEvent(config: ConversionConfig): ConversionEvent; } - - \ No newline at end of file +} From 59b7ce68d2d87a82fbfeedcd5b2bb0e8e4a6d088 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Thu, 24 Sep 2020 09:43:49 -0700 Subject: [PATCH 09/22] Fix imports` --- .../optimizely-sdk/lib/core/notification_center/index.d.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.d.ts b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts index b756c78fd..afc1cac57 100644 --- a/packages/optimizely-sdk/lib/core/notification_center/index.d.ts +++ b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts @@ -16,7 +16,6 @@ */ declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { - import { UserAttributes, Experiment, Variation } from '@optimizely/optimizely-sdk'; import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; export enum NOTIFICATION_TYPES { @@ -65,10 +64,10 @@ declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { export interface NotificationData { type?: DECISION_NOTIFICATION_TYPES; userId?: string; - attributes?: UserAttributes; + attributes?: import('../../shared_types').UserAttributes; decisionInfo?: DecisionInfo; - experiment?: Experiment; - variation?: Variation; + experiment?: import('../../shared_types').Experiment; + variation?: import('../../shared_types').Variation; logEvent?: string; eventKey?: string; eventTags?: string; From 2f4a24e2a518b1c6c1e279c98a9f1d2b85bac192 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Thu, 24 Sep 2020 09:49:37 -0700 Subject: [PATCH 10/22] Fix imports --- .../lib/core/event_builder/event_helpers.d.ts | 6 +++--- packages/optimizely-sdk/lib/core/event_builder/index.d.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts index 9333ac337..5d0d856c4 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts @@ -16,13 +16,13 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; - import { EventTags, UserAttributes } from '@optimizely/optimizely-sdk'; + import { EventTags } from '@optimizely/optimizely-sdk'; interface ImpressionConfig { experimentKey: string; variationKey: string; userId: string; - userAttributes: UserAttributes; + userAttributes: import('../../shared_types').UserAttributes; clientEngine: string; clientVersion: string; configObj: ProjectConfig; @@ -35,7 +35,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { eventKey: string; eventTags: EventTags; userId: string; - userAttributes: UserAttributes; + userAttributes: import('../../shared_types').UserAttributes; clientEngine: string; clientVersion: string; configObj: ProjectConfig; diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts index c9cc7c30c..09b486047 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts @@ -17,11 +17,11 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; import { LogHandler } from '@optimizely/js-sdk-logging'; - import { EventTags, UserAttributes } from '@optimizely/optimizely-sdk'; + import { EventTags } from '@optimizely/optimizely-sdk'; import { Event as EventLoggingEndpoint } from '@optimizely/optimizely-sdk'; interface ImpressionOptions { - attributes: UserAttributes; + attributes: import('../../shared_types').UserAttributes; clientEngine: string; clientVersion: string; configObj: ProjectConfig; @@ -33,7 +33,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { } interface ConversionEventOptions { - attributes: UserAttributes; + attributes: import('../../shared_types').UserAttributes; clientEngine: string; clientVersion: string; configObj: ProjectConfig; From b7aa82c72d9e6123ef24e8218e715e81ca1de5b3 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Thu, 24 Sep 2020 23:04:03 -0700 Subject: [PATCH 11/22] Draft: Convert optimizely module to ts --- .../event-processor/src/eventProcessor.ts | 2 +- .../lib/core/decision_service/index.d.ts | 2 +- .../lib/core/event_builder/event_helpers.d.ts | 32 +- .../lib/core/event_builder/index.d.ts | 30 +- .../lib/core/notification_center/index.d.ts | 13 +- .../lib/core/project_config/entities.ts | 13 +- .../lib/core/project_config/index.d.ts | 31 +- .../project_config_manager.d.ts | 6 +- packages/optimizely-sdk/lib/index.d.ts | 10 +- .../optimizely-sdk/lib/optimizely/index.js | 1271 ---------------- .../optimizely-sdk/lib/optimizely/index.ts | 1323 +++++++++++++++++ packages/optimizely-sdk/lib/shared_types.ts | 8 + 12 files changed, 1421 insertions(+), 1320 deletions(-) delete mode 100644 packages/optimizely-sdk/lib/optimizely/index.js create mode 100644 packages/optimizely-sdk/lib/optimizely/index.ts diff --git a/packages/event-processor/src/eventProcessor.ts b/packages/event-processor/src/eventProcessor.ts index 57826e41e..59b45abd6 100644 --- a/packages/event-processor/src/eventProcessor.ts +++ b/packages/event-processor/src/eventProcessor.ts @@ -31,7 +31,7 @@ export type ProcessableEvent = ConversionEvent | ImpressionEvent export type EventDispatchResult = { result: boolean; event: ProcessableEvent } export interface EventProcessor extends Managed { - process(event: ProcessableEvent): void + process(event: Partial): void } export function validateAndGetFlushInterval(flushInterval: number): number { diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.d.ts b/packages/optimizely-sdk/lib/core/decision_service/index.d.ts index 0daf24b22..29f5aee1c 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.d.ts +++ b/packages/optimizely-sdk/lib/core/decision_service/index.d.ts @@ -25,7 +25,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/decision_service' { */ export function createDecisionService(options: Options): DecisionService; - interface DecisionService { + export interface DecisionService { /** * Gets variation where visitor will be bucketed. diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts index 5d0d856c4..25731a687 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts @@ -22,7 +22,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { experimentKey: string; variationKey: string; userId: string; - userAttributes: import('../../shared_types').UserAttributes; + userAttributes?: import('../../shared_types').UserAttributes; clientEngine: string; clientVersion: string; configObj: ProjectConfig; @@ -33,9 +33,9 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { interface ConversionConfig { eventKey: string; - eventTags: EventTags; + eventTags?: EventTags; userId: string; - userAttributes: import('../../shared_types').UserAttributes; + userAttributes?: import('../../shared_types').UserAttributes; clientEngine: string; clientVersion: string; configObj: ProjectConfig; @@ -44,18 +44,16 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { // eslint-disable-next-line @typescript-eslint/no-empty-interface interface ConversionEvent {} - export interface EventHelpers { - /** - * Creates an ImpressionEvent object from decision data - * @param {ImpressionConfig} config - * @return {ImpressionEvent} an ImpressionEvent object - */ - buildImpressionEvent(config: ImpressionConfig): ImpressionEvent; - /** - * Creates a ConversionEvent object from track - * @param {ConversionConfig} config - * @return {ConversionEvent} a ConversionEvent object - */ - buildConversionEvent(config: ConversionConfig): ConversionEvent; - } + /** + * Creates an ImpressionEvent object from decision data + * @param {ImpressionConfig} config + * @return {ImpressionEvent} an ImpressionEvent object + */ + export function buildImpressionEvent(config: ImpressionConfig): ImpressionEvent; + /** + * Creates a ConversionEvent object from track + * @param {ConversionConfig} config + * @return {ConversionEvent} a ConversionEvent object + */ + export function buildConversionEvent(config: ConversionConfig): ConversionEvent; } diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts index 09b486047..ddd21b667 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts @@ -21,12 +21,12 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { import { Event as EventLoggingEndpoint } from '@optimizely/optimizely-sdk'; interface ImpressionOptions { - attributes: import('../../shared_types').UserAttributes; + attributes?: import('../../shared_types').UserAttributes; clientEngine: string; clientVersion: string; configObj: ProjectConfig; experimentId: string; - eventKey: string; + eventKey?: string; variationId: string; logger: LogHandler; userId: string; @@ -43,18 +43,16 @@ declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { eventTags: EventTags; } - export interface EventBuilder { - /** - * Create impression event params to be sent to the logging endpoint - * @param {ImpressionOptions} options Object containing values needed to build impression event - * @return {EventLoggingEndpoint} Params to be used in impression event logging endpoint call - */ - getImpressionEvent(options: ImpressionOptions): EventLoggingEndpoint; - /** - * Create conversion event params to be sent to the logging endpoint - * @param {ConversionEventOptions} options Object containing values needed to build conversion event - * @return {EventLoggingEndpoint} Params to be used in conversion event logging endpoint call - */ - getConversionEvent(options: ConversionEventOptions): EventLoggingEndpoint; - } + /** + * Create impression event params to be sent to the logging endpoint + * @param {ImpressionOptions} options Object containing values needed to build impression event + * @return {EventLoggingEndpoint} Params to be used in impression event logging endpoint call + */ + export function getImpressionEvent(options: ImpressionOptions): EventLoggingEndpoint; + /** + * Create conversion event params to be sent to the logging endpoint + * @param {ConversionEventOptions} options Object containing values needed to build conversion event + * @return {EventLoggingEndpoint} Params to be used in conversion event logging endpoint call + */ + export function getConversionEvent(options: ConversionEventOptions): EventLoggingEndpoint; } diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.d.ts b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts index afc1cac57..85f001320 100644 --- a/packages/optimizely-sdk/lib/core/notification_center/index.d.ts +++ b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts @@ -17,7 +17,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; - + import { Event, EventTags } from '@optimizely/optimizely-sdk'; export enum NOTIFICATION_TYPES { ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', DECISION = 'DECISION:type, userId, attributes, decisionInfo', @@ -50,7 +50,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { export type DecisionInfo = { experimentKey?: string; - variationKey?: string; + variationKey?: string | null; featureKey?: string; featureEnabled?: boolean; source?: string; @@ -62,15 +62,16 @@ declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { }; export interface NotificationData { - type?: DECISION_NOTIFICATION_TYPES; + // type?: DECISION_NOTIFICATION_TYPES; + type?: string; userId?: string; attributes?: import('../../shared_types').UserAttributes; decisionInfo?: DecisionInfo; experiment?: import('../../shared_types').Experiment; variation?: import('../../shared_types').Variation; - logEvent?: string; + logEvent?: Event; eventKey?: string; - eventTags?: string; + eventTags?: EventTags; } export interface NotificationCenter { @@ -80,7 +81,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { * @param {NOTIFICATION_TYPES} notificationType One of NOTIFICATION_TYPES * @param {NotificationData} notificationData Will be passed to callbacks called */ - sendNotifications(notificationType: NOTIFICATION_TYPES, notificationData: NotificationData): void; + sendNotifications(notificationType: NOTIFICATION_TYPES, notificationData?: NotificationData): void; } /** diff --git a/packages/optimizely-sdk/lib/core/project_config/entities.ts b/packages/optimizely-sdk/lib/core/project_config/entities.ts index 71d3fab1c..a9817a495 100644 --- a/packages/optimizely-sdk/lib/core/project_config/entities.ts +++ b/packages/optimizely-sdk/lib/core/project_config/entities.ts @@ -16,9 +16,20 @@ export interface FeatureVariable { type: string; + key: string; + id: string; + defaultValue: string; } export interface FeatureFlag { - variables: FeatureVariable[]; + rolloutId: string; + key: string; + id: string; + experimentIds: string[], + variables: FeatureVariable[], + variableKeyMap?: {[key: string]: FeatureVariable} } +export interface FeatureKeyMap { + [key: string]: FeatureFlag +} diff --git a/packages/optimizely-sdk/lib/core/project_config/index.d.ts b/packages/optimizely-sdk/lib/core/project_config/index.d.ts index c92f06b25..0119aaa7f 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.d.ts +++ b/packages/optimizely-sdk/lib/core/project_config/index.d.ts @@ -15,10 +15,37 @@ */ declare module '@optimizely/optimizely-sdk/lib/core/project_config' { - import { LogHandler } from '@optimizely/js-sdk-logging'; + import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; + import { EventDispatcher, DatafileOptions } from '@optimizely/optimizely-sdk'; // eslint-disable-next-line @typescript-eslint/no-empty-interface - export interface ProjectConfig {} + export interface ProjectConfig { + revision: string; + projectId: string; + featureKeyMap?: { + [key: string]: import('./entities').FeatureFlag + }; + clientEngine: string; + clientVersion?: string; + errorHandler: ErrorHandler; + eventDispatcher: EventDispatcher; + isValidInstance: boolean; + // TODO[OASIS-6649]: Don't use object type + // eslint-disable-next-line @typescript-eslint/ban-types + datafile: object | string; + // TODO[OASIS-6649]: Don't use object type + // eslint-disable-next-line @typescript-eslint/ban-types + jsonSchemaValidator?: object; + sdkKey?: string; + userProfileService?: import('../../shared_types').UserProfileService | null; + UNSTABLE_conditionEvaluators?: unknown; + eventFlushInterval?: number; + eventBatchSize?: number; + datafileOptions?: DatafileOptions; + eventMaxQueueSize?: number; + logger: LogHandler; + experimentKeyMap:{[key: string]: import('../../shared_types').Experiment}; + } /** * Determine for given experiment if event is running, which determines whether should be dispatched or not * @param {ProjectConfig} configObj Object representing project configuration diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts index 5c586558e..785a9bd63 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts +++ b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts @@ -28,9 +28,9 @@ declare module '@optimizely/optimizely-sdk/lib/core/projet_config_manager' { * @param {Object} config.jsonSchemaValidator * @param {string} config.sdkKey */ - export function ProjectConfigManager(config: ProjectConfig): ProjectConfigManager; + export function createProjectConfigManager(config: Partial): ProjectConfigManager; - interface ProjectConfigManager { + export interface ProjectConfigManager { /** * Returns the current project config object, or null if no project config object @@ -46,7 +46,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/projet_config_manager' { * @param {Function} listener * @return {Function} */ - onUpdate(): (listener: (config: ProjectConfig) => void) => () => void; + onUpdate(listener: (config: ProjectConfig) => void): () => void; /** * Returns a Promise that fulfills when this ProjectConfigManager is ready to diff --git a/packages/optimizely-sdk/lib/index.d.ts b/packages/optimizely-sdk/lib/index.d.ts index 3bc79687c..867de1af5 100644 --- a/packages/optimizely-sdk/lib/index.d.ts +++ b/packages/optimizely-sdk/lib/index.d.ts @@ -30,7 +30,7 @@ declare module '@optimizely/optimizely-sdk' { export const eventDispatcher: EventDispatcher; - interface DatafileOptions { + export interface DatafileOptions { autoUpdate?: boolean; updateInterval?: number; urlTemplate?: string; @@ -149,6 +149,12 @@ declare module '@optimizely/optimizely-sdk' { params: any; } + export type EventDispatcherResponse = { + statusCode: number + } + + export type EventDispatcherCallback = (response: EventDispatcherResponse) => void + export interface EventDispatcher { /** * @param event @@ -157,7 +163,7 @@ declare module '@optimizely/optimizely-sdk' { * After the event has at least been queued for dispatch, call this function to return * control back to the Client. */ - dispatchEvent: (event: Event, callback: () => void) => void; + dispatchEvent: (event: Event, callback: EventDispatcherCallback) => void; } // NotificationCenter-related types diff --git a/packages/optimizely-sdk/lib/optimizely/index.js b/packages/optimizely-sdk/lib/optimizely/index.js deleted file mode 100644 index fd15337da..000000000 --- a/packages/optimizely-sdk/lib/optimizely/index.js +++ /dev/null @@ -1,1271 +0,0 @@ -/**************************************************************************** - * Copyright 2016-2020, 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 { sprintf, objectValues } from '@optimizely/js-sdk-utils'; -import * as eventProcessor from '@optimizely/js-sdk-event-processor'; - -import { isSafeInteger } from '../utils/fns' -import { validate } from '../utils/attributes_validator'; -import decisionService from '../core/decision_service'; -import * as enums from '../utils/enums'; -import { getImpressionEvent, getConversionEvent } from '../core/event_builder/index.js'; -import { buildConversionEvent, buildImpressionEvent } from '../core/event_builder/event_helpers'; -import * as eventTagsValidator from '../utils/event_tags_validator'; -import notificationCenter from '../core/notification_center'; -import projectConfig from '../core/project_config'; -import * as userProfileServiceValidator from '../utils/user_profile_service_validator'; -import * as stringValidator from '../utils/string_value_validator'; -import projectConfigManager from '../core/project_config/project_config_manager'; - -var ERROR_MESSAGES = enums.ERROR_MESSAGES; -var LOG_LEVEL = enums.LOG_LEVEL; -var LOG_MESSAGES = enums.LOG_MESSAGES; -var MODULE_NAME = 'OPTIMIZELY'; -var DECISION_SOURCES = enums.DECISION_SOURCES; -var FEATURE_VARIABLE_TYPES = enums.FEATURE_VARIABLE_TYPES; -var DECISION_NOTIFICATION_TYPES = enums.DECISION_NOTIFICATION_TYPES; -var NOTIFICATION_TYPES = enums.NOTIFICATION_TYPES; - -var DEFAULT_ONREADY_TIMEOUT = 30000; - -/** - * The Optimizely class - * @param {Object} config - * @param {string} config.clientEngine - * @param {string} config.clientVersion - * @param {Object|string} config.datafile - * @param {Object} config.errorHandler - * @param {Object} config.eventDispatcher - * @param {Object} config.logger - * @param {Object} config.userProfileService - * @param {Object} config.eventBatchSize - * @param {Object} config.eventFlushInterval - * @param {string} config.sdkKey - */ -function Optimizely(config) { - var clientEngine = config.clientEngine; - if (enums.VALID_CLIENT_ENGINES.indexOf(clientEngine) === -1) { - config.logger.log( - LOG_LEVEL.INFO, - sprintf(LOG_MESSAGES.INVALID_CLIENT_ENGINE, MODULE_NAME, clientEngine) - ); - clientEngine = enums.NODE_CLIENT_ENGINE; - } - - this.clientEngine = clientEngine; - this.clientVersion = config.clientVersion || enums.NODE_CLIENT_VERSION; - this.errorHandler = config.errorHandler; - this.eventDispatcher = config.eventDispatcher; - this.__isOptimizelyConfigValid = config.isValidInstance; - this.logger = config.logger; - - this.projectConfigManager = new projectConfigManager.ProjectConfigManager({ - datafile: config.datafile, - datafileOptions: config.datafileOptions, - jsonSchemaValidator: config.jsonSchemaValidator, - sdkKey: config.sdkKey, - }); - - this.__disposeOnUpdate = this.projectConfigManager.onUpdate( - function(configObj) { - this.logger.log( - LOG_LEVEL.INFO, - sprintf(LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, MODULE_NAME, configObj.revision, configObj.projectId) - ); - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); - }.bind(this) - ); - - var projectConfigManagerReadyPromise = this.projectConfigManager.onReady(); - - var userProfileService = null; - if (config.userProfileService) { - try { - if (userProfileServiceValidator.validate(config.userProfileService)) { - userProfileService = config.userProfileService; - this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.VALID_USER_PROFILE_SERVICE, MODULE_NAME)); - } - } catch (ex) { - this.logger.log(LOG_LEVEL.WARNING, ex.message); - } - } - - this.decisionService = decisionService.createDecisionService({ - userProfileService: userProfileService, - logger: this.logger, - UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators, - }); - - this.notificationCenter = notificationCenter.createNotificationCenter({ - logger: this.logger, - errorHandler: this.errorHandler, - }); - - this.eventProcessor = new eventProcessor.LogTierV1EventProcessor({ - dispatcher: this.eventDispatcher, - flushInterval: config.eventFlushInterval, - batchSize: config.eventBatchSize, - maxQueueSize: config.eventMaxQueueSize, - notificationCenter: this.notificationCenter, - }); - - var eventProcessorStartedPromise = this.eventProcessor.start(); - - this.__readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then(function(promiseResults) { - // Only return status from project config promise because event processor promise does not return any status. - return promiseResults[0]; - }) - - this.__readyTimeouts = {}; - this.__nextReadyTimeoutId = 0; -} - -/** - * Returns a truthy value if this instance currently has a valid project config - * object, and the initial configuration object that was passed into the - * constructor was also valid. - * @return {*} - */ -Optimizely.prototype.__isValidInstance = function() { - return this.__isOptimizelyConfigValid && this.projectConfigManager.getConfig(); -}; - -/** - * Buckets visitor and sends impression event to Optimizely. - * @param {string} experimentKey - * @param {string} userId - * @param {Object} attributes - * @return {string|null} variation key - */ -Optimizely.prototype.activate = function(experimentKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'activate')); - return null; - } - - if (!this.__validateInputs({ experiment_key: experimentKey, user_id: userId }, attributes)) { - return this.__notActivatingExperiment(experimentKey, userId); - } - - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - - try { - var variationKey = this.getVariation(experimentKey, userId, attributes); - if (variationKey === null) { - return this.__notActivatingExperiment(experimentKey, userId); - } - - // If experiment is not set to 'Running' status, log accordingly and return variation key - if (!projectConfig.isRunning(configObj, experimentKey)) { - var shouldNotDispatchActivateLogMessage = sprintf( - LOG_MESSAGES.SHOULD_NOT_DISPATCH_ACTIVATE, - MODULE_NAME, - experimentKey - ); - this.logger.log(LOG_LEVEL.DEBUG, shouldNotDispatchActivateLogMessage); - return variationKey; - } - - this._sendImpressionEvent(experimentKey, variationKey, userId, attributes); - - return variationKey; - } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - var failedActivationLogMessage = sprintf( - LOG_MESSAGES.NOT_ACTIVATING_USER, - MODULE_NAME, - userId, - experimentKey - ); - this.logger.log(LOG_LEVEL.INFO, failedActivationLogMessage); - this.errorHandler.handleError(ex); - return null; - } - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Create an impression event and call the event dispatcher's dispatch method to - * send this event to Optimizely. Then use the notification center to trigger - * any notification listeners for the ACTIVATE notification type. - * @param {string} experimentKey Key of experiment that was activated - * @param {string} variationKey Key of variation shown in experiment that was activated - * @param {string} userId ID of user to whom the variation was shown - * @param {Object} attributes Optional user attributes - */ -Optimizely.prototype._sendImpressionEvent = function(experimentKey, variationKey, userId, attributes) { - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return; - } - - var impressionEvent = buildImpressionEvent({ - experimentKey: experimentKey, - variationKey: variationKey, - userId: userId, - userAttributes: attributes, - clientEngine: this.clientEngine, - clientVersion: this.clientVersion, - configObj: configObj, - }); - // TODO is it okay to not pass a projectConfig as second argument - this.eventProcessor.process(impressionEvent); - this.__emitNotificationCenterActivate(experimentKey, variationKey, userId, attributes); -}; - -/** - * Emit the ACTIVATE notification on the notificationCenter - * @param {string} experimentKey Key of experiment that was activated - * @param {string} variationKey Key of variation shown in experiment that was activated - * @param {string} userId ID of user to whom the variation was shown - * @param {Object} attributes Optional user attributes - */ -Optimizely.prototype.__emitNotificationCenterActivate = function(experimentKey, variationKey, userId, attributes) { - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return; - } - - var variationId = projectConfig.getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey); - var experimentId = projectConfig.getExperimentId(configObj, experimentKey); - var impressionEventOptions = { - attributes: attributes, - clientEngine: this.clientEngine, - clientVersion: this.clientVersion, - configObj: configObj, - experimentId: experimentId, - userId: userId, - variationId: variationId, - logger: this.logger, - }; - var impressionEvent = getImpressionEvent(impressionEventOptions); - var experiment = configObj.experimentKeyMap[experimentKey]; - var variation; - if (experiment && experiment.variationKeyMap) { - variation = experiment.variationKeyMap[variationKey]; - } - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, { - experiment: experiment, - userId: userId, - attributes: attributes, - variation: variation, - logEvent: impressionEvent, - }); -}; - -/** - * Sends conversion event to Optimizely. - * @param {string} eventKey - * @param {string} userId - * @param {string} attributes - * @param {Object} eventTags Values associated with the event. - */ -Optimizely.prototype.track = function(eventKey, userId, attributes, eventTags) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'track')); - return; - } - - if (!this.__validateInputs({ user_id: userId, event_key: eventKey }, attributes, eventTags)) { - return; - } - - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return; - } - - if (!projectConfig.eventWithKeyExists(configObj, eventKey)) { - this.logger.log( - LOG_LEVEL.WARNING, - sprintf(enums.LOG_MESSAGES.EVENT_KEY_NOT_FOUND, MODULE_NAME, eventKey) - ); - this.logger.log(LOG_LEVEL.WARNING, sprintf(LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId)); - return; - } - - // remove null values from eventTags - eventTags = this.__filterEmptyValues(eventTags); - var conversionEvent = buildConversionEvent({ - eventKey: eventKey, - eventTags: eventTags, - userId: userId, - userAttributes: attributes, - clientEngine: this.clientEngine, - clientVersion: this.clientVersion, - configObj: configObj, - }); - this.logger.log(LOG_LEVEL.INFO, sprintf(enums.LOG_MESSAGES.TRACK_EVENT, MODULE_NAME, eventKey, userId)); - // TODO is it okay to not pass a projectConfig as second argument - this.eventProcessor.process(conversionEvent); - this.__emitNotificationCenterTrack(eventKey, userId, attributes, eventTags); - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - var failedTrackLogMessage = sprintf(LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); - this.logger.log(LOG_LEVEL.ERROR, failedTrackLogMessage); - } -}; - -/** - * Send TRACK event to notificationCenter - * @param {string} eventKey - * @param {string} userId - * @param {string} attributes - * @param {Object} eventTags Values associated with the event. - */ -Optimizely.prototype.__emitNotificationCenterTrack = function(eventKey, userId, attributes, eventTags) { - try { - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return; - } - - var conversionEventOptions = { - attributes: attributes, - clientEngine: this.clientEngine, - clientVersion: this.clientVersion, - configObj: configObj, - eventKey: eventKey, - eventTags: eventTags, - logger: this.logger, - userId: userId, - }; - var conversionEvent = getConversionEvent(conversionEventOptions); - - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.TRACK, { - eventKey: eventKey, - userId: userId, - attributes: attributes, - eventTags: eventTags, - logEvent: conversionEvent, - }); - } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); - } -}; - -/** - * Gets variation where visitor will be bucketed. - * @param {string} experimentKey - * @param {string} userId - * @param {Object} attributes - * @return {string|null} variation key - */ -Optimizely.prototype.getVariation = function(experimentKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getVariation')); - return null; - } - - try { - if (!this.__validateInputs({ experiment_key: experimentKey, user_id: userId }, attributes)) { - return null; - } - - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - - var experiment = configObj.experimentKeyMap[experimentKey]; - if (!experiment) { - this.logger.log( - LOG_LEVEL.DEBUG, - sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey) - ); - return null; - } - - var variationKey = this.decisionService.getVariation(configObj, experimentKey, userId, attributes); - var decisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) - ? DECISION_NOTIFICATION_TYPES.FEATURE_TEST - : DECISION_NOTIFICATION_TYPES.AB_TEST; - - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { - type: decisionNotificationType, - userId: userId, - attributes: attributes || {}, - decisionInfo: { - experimentKey: experimentKey, - variationKey: variationKey, - }, - }); - - return variationKey; - } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); - return null; - } - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Force a user into a variation for a given experiment. - * @param {string} experimentKey - * @param {string} userId - * @param {string|null} variationKey user will be forced into. If null, then clear the existing experiment-to-variation mapping. - * @return boolean A boolean value that indicates if the set completed successfully. - */ -Optimizely.prototype.setForcedVariation = function(experimentKey, userId, variationKey) { - if (!this.__validateInputs({ experiment_key: experimentKey, user_id: userId })) { - return false; - } - - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return false; - } - - try { - return this.decisionService.setForcedVariation(configObj, experimentKey, userId, variationKey); - } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); - return false; - } -}; - -/** - * Gets the forced variation for a given user and experiment. - * @param {string} experimentKey - * @param {string} userId - * @return {string|null} The forced variation key. - */ -Optimizely.prototype.getForcedVariation = function(experimentKey, userId) { - if (!this.__validateInputs({ experiment_key: experimentKey, user_id: userId })) { - return null; - } - - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - - try { - return this.decisionService.getForcedVariation(configObj, experimentKey, userId); - } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); - return null; - } -}; - -/** - * Validate string inputs, user attributes and event tags. - * @param {string} stringInputs Map of string keys and associated values - * @param {Object} userAttributes Optional parameter for user's attributes - * @param {Object} eventTags Optional parameter for event tags - * @return {boolean} True if inputs are valid - * - */ -Optimizely.prototype.__validateInputs = function(stringInputs, userAttributes, eventTags) { - try { - // Null, undefined or non-string user Id is invalid. - if (stringInputs.hasOwnProperty('user_id')) { - var userId = stringInputs.user_id; - if (typeof userId !== 'string' || userId === null || userId === 'undefined') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, 'user_id')); - } - - delete stringInputs.user_id; - } - - var inputKeys = Object.keys(stringInputs); - for (var index = 0; index < inputKeys.length; index++) { - var key = inputKeys[index]; - if (!stringValidator.validate(stringInputs[key])) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, key)); - } - } - if (userAttributes) { - validate(userAttributes); - } - if (eventTags) { - eventTagsValidator.validate(eventTags); - } - return true; - } catch (ex) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); - return false; - } -}; - -/** - * Shows failed activation log message and returns null when user is not activated in experiment - * @param experimentKey - * @param userId - * @return {null} - */ -Optimizely.prototype.__notActivatingExperiment = function(experimentKey, userId) { - var failedActivationLogMessage = sprintf( - LOG_MESSAGES.NOT_ACTIVATING_USER, - MODULE_NAME, - userId, - experimentKey - ); - this.logger.log(LOG_LEVEL.INFO, failedActivationLogMessage); - return null; -}; - -/** - * Filters out attributes/eventTags with null or undefined values - * @param map - * @returns {Object} map - */ -Optimizely.prototype.__filterEmptyValues = function(map) { - for (var key in map) { - if (map.hasOwnProperty(key) && (map[key] === null || map[key] === undefined)) { - delete map[key]; - } - } - return map; -}; - -/** - * Returns true if the feature is enabled for the given user. - * @param {string} featureKey Key of feature which will be checked - * @param {string} userId ID of user which will be checked - * @param {Object} attributes Optional user attributes - * @return {boolean} True if the feature is enabled for the user, false otherwise - */ -Optimizely.prototype.isFeatureEnabled = function(featureKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log( - LOG_LEVEL.ERROR, - sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'isFeatureEnabled') - ); - return false; - } - - if (!this.__validateInputs({ feature_key: featureKey, user_id: userId }, attributes)) { - return false; - } - - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return false; - } - - var feature = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); - if (!feature) { - return false; - } - - var featureEnabled = false; - var decision = this.decisionService.getVariationForFeature(configObj, feature, userId, attributes); - var variation = decision.variation; - var sourceInfo = {}; - - if (variation) { - featureEnabled = variation.featureEnabled; - if (decision.decisionSource === DECISION_SOURCES.FEATURE_TEST) { - sourceInfo = { - experimentKey: decision.experiment.key, - variationKey: decision.variation.key, - }; - // got a variation from the exp, so we track the impression - this._sendImpressionEvent(decision.experiment.key, decision.variation.key, userId, attributes); - } - } - - if (featureEnabled === true) { - this.logger.log( - LOG_LEVEL.INFO, - sprintf(LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId) - ); - } else { - this.logger.log( - LOG_LEVEL.INFO, - sprintf(LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId) - ); - featureEnabled = false; - } - - var featureInfo = { - featureKey: featureKey, - featureEnabled: featureEnabled, - source: decision.decisionSource, - sourceInfo: sourceInfo, - }; - - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { - type: DECISION_NOTIFICATION_TYPES.FEATURE, - userId: userId, - attributes: attributes || {}, - decisionInfo: featureInfo, - }); - - return featureEnabled; - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return false; - } -}; - -/** - * Returns an Array containing the keys of all features in the project that are - * enabled for the given user. - * @param {string} userId - * @param {Object} attributes - * @return {Array} Array of feature keys (strings) - */ -Optimizely.prototype.getEnabledFeatures = function(userId, attributes) { - try { - var enabledFeatures = []; - if (!this.__isValidInstance()) { - this.logger.log( - LOG_LEVEL.ERROR, - sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getEnabledFeatures') - ); - return enabledFeatures; - } - - if (!this.__validateInputs({ user_id: userId })) { - return enabledFeatures; - } - - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return enabledFeatures; - } - - objectValues(configObj.featureKeyMap).forEach( - function(feature) { - if (this.isFeatureEnabled(feature.key, userId, attributes)) { - enabledFeatures.push(feature.key); - } - }.bind(this) - ); - - return enabledFeatures; - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return []; - } -}; - -/** - * Returns dynamically-typed value of the variable attached to the given - * feature flag. Returns null if the feature key or variable key is invalid. - * - * @param {string} featureKey Key of the feature whose variable's - * value is being accessed - * @param {string} variableKey Key of the variable whose value is - * being accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {string|boolean|number|null} Value of the variable cast to the appropriate - * type, or null if the feature key is invalid or - * the variable key is invalid - */ - -Optimizely.prototype.getFeatureVariable = function(featureKey, variableKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariable')); - return null; - } - return this._getFeatureVariableForType(featureKey, variableKey, null, userId, attributes); - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Helper method to get the value for a variable of a certain type attached to a - * feature flag. Returns null if the feature key is invalid, the variable key is - * invalid, the given variable type does not match the variable's actual type, - * or the variable value cannot be cast to the required type. If the given variable - * type is null, the value of the variable cast to the appropriate type is returned. - * - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string|null} variableType Type of the variable whose value is being - * accessed (must be one of FEATURE_VARIABLE_TYPES - * in lib/utils/enums/index.js), or null to return the - * value of the variable cast to the appropriate type - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {string|boolean|number|null} Value of the variable cast to the appropriate - * type, or null if the feature key is invalid, the - * variable key is invalid, or there is a mismatch - * with the type of the variable - */ -Optimizely.prototype._getFeatureVariableForType = function(featureKey, variableKey, variableType, userId, attributes) { - if (!this.__validateInputs({ feature_key: featureKey, variable_key: variableKey, user_id: userId }, attributes)) { - return null; - } - - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - - var featureFlag = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); - if (!featureFlag) { - return null; - } - - var variable = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, this.logger); - if (!variable) { - return null; - } - - if (variableType && variable.type !== variableType) { - this.logger.log( - LOG_LEVEL.WARNING, - sprintf(LOG_MESSAGES.VARIABLE_REQUESTED_WITH_WRONG_TYPE, MODULE_NAME, variableType, variable.type) - ); - return null; - } - - var decision = this.decisionService.getVariationForFeature(configObj, featureFlag, userId, attributes); - var featureEnabled = decision.variation !== null ? decision.variation.featureEnabled : false; - var variableValue = this._getFeatureVariableValueFromVariation(featureKey, featureEnabled, decision.variation, variable, userId); - - var sourceInfo = {}; - if (decision.decisionSource === DECISION_SOURCES.FEATURE_TEST) { - sourceInfo = { - experimentKey: decision.experiment.key, - variationKey: decision.variation.key, - }; - } - - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { - type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, - userId: userId, - attributes: attributes || {}, - decisionInfo: { - featureKey: featureKey, - featureEnabled: featureEnabled, - source: decision.decisionSource, - variableKey: variableKey, - variableValue: variableValue, - variableType: variable.type, - sourceInfo: sourceInfo, - }, - }); - return variableValue; -}; - -/** - * Helper method to get the non type-casted value for a variable attached to a - * feature flag. Returns appropriate variable value depending on whether there - * was a matching variation, feature was enabled or not or varible was part of the - * available variation or not. Also logs the appropriate message explaining how it - * evaluated the value of the variable. - * - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {boolean} featureEnabled Boolean indicating if feature is enabled or not - * @param {object} variation variation returned by decision service - * @param {object} variable varible whose value is being evaluated - * @param {string} userId ID for the user - * @return {string|null} String value of the variable or null if the config Obj - * is null - */ -Optimizely.prototype._getFeatureVariableValueFromVariation = function(featureKey, featureEnabled, variation, variable, userId) { - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - - var variableValue = variable.defaultValue; - if (variation !== null) { - var value = projectConfig.getVariableValueForVariation(configObj, variable, variation, this.logger); - if (value !== null) { - if (featureEnabled) { - variableValue = value; - this.logger.log( - LOG_LEVEL.INFO, - sprintf( - LOG_MESSAGES.USER_RECEIVED_VARIABLE_VALUE, - MODULE_NAME, - variableValue, - variable.key, - featureKey - ) - ); - } else { - this.logger.log( - LOG_LEVEL.INFO, - sprintf( - LOG_MESSAGES.FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, - MODULE_NAME, - featureKey, - userId, - variableValue - ) - ); - } - } else { - this.logger.log( - LOG_LEVEL.INFO, - sprintf( - LOG_MESSAGES.VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, - MODULE_NAME, - variable.key, - variation.key - ) - ); - } - } else { - this.logger.log( - LOG_LEVEL.INFO, - sprintf( - LOG_MESSAGES.USER_RECEIVED_DEFAULT_VARIABLE_VALUE, - MODULE_NAME, - userId, - variable.key, - featureKey - ) - ); - } - - return projectConfig.getTypeCastValue(variableValue, variable.type, this.logger); -} - -/** - * Returns value for the given boolean variable attached to the given feature - * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {boolean|null} Boolean value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable - */ -Optimizely.prototype.getFeatureVariableBoolean = function(featureKey, variableKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableBoolean')); - return null; - } - return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.BOOLEAN, userId, attributes); - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Returns value for the given double variable attached to the given feature - * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {number|null} Number value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable - */ -Optimizely.prototype.getFeatureVariableDouble = function(featureKey, variableKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableDouble')); - return null; - } - return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.DOUBLE, userId, attributes); - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Returns value for the given integer variable attached to the given feature - * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {number|null} Number value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable - */ -Optimizely.prototype.getFeatureVariableInteger = function(featureKey, variableKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableInteger')); - return null; - } - return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.INTEGER, userId, attributes); - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Returns value for the given string variable attached to the given feature - * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {string|null} String value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable - */ -Optimizely.prototype.getFeatureVariableString = function(featureKey, variableKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString')); - return null; - } - return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.STRING, userId, attributes); - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Returns value for the given json variable attached to the given feature - * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {object|null} Object value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable - */ -Optimizely.prototype.getFeatureVariableJSON = function(featureKey, variableKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableJSON')); - return null; - } - return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.JSON, userId, attributes); - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Returns values for all the variables attached to the given feature - * flag. - * @param {string} featureKey Key of the feature whose variables are being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {object|null} Object containing all the variables, or null if the - * feature key is invalid - */ -Optimizely.prototype.getAllFeatureVariables = function(featureKey, userId, attributes) { - try { - if (!this.__isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getAllFeatureVariables')); - return null; - } - - if (!this.__validateInputs({ feature_key: featureKey, user_id: userId }, attributes)) { - return null; - } - - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - - var featureFlag = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); - if (!featureFlag) { - return null; - } - - var decision = this.decisionService.getVariationForFeature(configObj, featureFlag, userId, attributes); - var featureEnabled = decision.variation !== null ? decision.variation.featureEnabled : false; - var allVariables = {}; - - featureFlag.variables.forEach(function (variable) { - allVariables[variable.key] = this._getFeatureVariableValueFromVariation(featureKey, featureEnabled, decision.variation, variable, userId); - }.bind(this)); - - var sourceInfo = {}; - if (decision.decisionSource === DECISION_SOURCES.FEATURE_TEST) { - sourceInfo = { - experimentKey: decision.experiment.key, - variationKey: decision.variation.key, - }; - } - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { - type: DECISION_NOTIFICATION_TYPES.ALL_FEATURE_VARIABLES, - userId: userId, - attributes: attributes || {}, - decisionInfo: { - featureKey: featureKey, - featureEnabled: featureEnabled, - source: decision.decisionSource, - variableValues: allVariables, - sourceInfo: sourceInfo, - }, - }); - - return allVariables; - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Returns OptimizelyConfig object containing experiments and features data - * @return {Object} - * - * OptimizelyConfig Object Schema - * { - * 'experimentsMap': { - * 'my-fist-experiment': { - * 'id': '111111', - * 'key': 'my-fist-experiment' - * 'variationsMap': { - * 'variation_1': { - * 'id': '121212', - * 'key': 'variation_1', - * 'variablesMap': { - * 'age': { - * 'id': '222222', - * 'key': 'age', - * 'type': 'integer', - * 'value': '0', - * } - * } - * } - * } - * } - * }, - * 'featuresMap': { - * 'awesome-feature': { - * 'id': '333333', - * 'key': 'awesome-feature', - * 'experimentsMap': Object, - * 'variationsMap': Object, - * } - * } - * } - */ -Optimizely.prototype.getOptimizelyConfig = function() { - try { - var configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - return this.projectConfigManager.getOptimizelyConfig(); - } catch (e) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return null; - } -}; - -/** - * Stop background processes belonging to this instance, including: - * - * - Active datafile requests - * - Pending datafile requests - * - Pending event queue flushes - * - * In-flight datafile requests will be aborted. Any events waiting to be sent - * as part of a batched event request will be immediately flushed to the event - * dispatcher. - * - * Returns a Promise that fulfills after all in-flight event dispatcher requests - * (including any final request resulting from flushing the queue as described - * above) are complete. If there are no in-flight event dispatcher requests and - * no queued events waiting to be sent, returns an immediately-fulfilled Promise. - * - * Returned Promises are fulfilled with result objects containing these - * properties: - * - success (boolean): true if the event dispatcher signaled completion of - * all in-flight and final requests, or if there were no - * queued events and no in-flight requests. false if an - * unexpected error was encountered during the close - * process. - * - reason (string=): If success is false, this is a string property with - * an explanatory message. - * - * NOTE: After close is called, this instance is no longer usable - any events - * generated will no longer be sent to the event dispatcher. - * - * @return {Promise} - */ -Optimizely.prototype.close = function() { - try { - var eventProcessorStoppedPromise = this.eventProcessor.stop(); - if (this.__disposeOnUpdate) { - this.__disposeOnUpdate(); - this.__disposeOnUpdate = null; - } - if (this.projectConfigManager) { - this.projectConfigManager.stop(); - } - Object.keys(this.__readyTimeouts).forEach( - function(readyTimeoutId) { - var readyTimeoutRecord = this.__readyTimeouts[readyTimeoutId]; - clearTimeout(readyTimeoutRecord.readyTimeout); - readyTimeoutRecord.onClose(); - }.bind(this) - ); - this.__readyTimeouts = {}; - return eventProcessorStoppedPromise.then( - function() { - return { - success: true, - }; - }, - function(err) { - return { - success: false, - reason: String(err), - }; - } - ); - } catch (err) { - this.logger.log(LOG_LEVEL.ERROR, err.message); - this.errorHandler.handleError(err); - return Promise.resolve({ - success: false, - reason: String(err), - }); - } -}; - -/** - * Returns a Promise that fulfills when this instance is ready to use (meaning - * it has a valid datafile), or has failed to become ready within a period of - * time (configurable by the timeout property of the options argument), or when - * this instance is closed via the close method. - * - * If a valid datafile was provided in the constructor, the returned Promise is - * immediately fulfilled. If an sdkKey was provided, a manager will be used to - * fetch a datafile, and the returned promise will fulfill if that fetch - * succeeds or fails before the timeout. The default timeout is 30 seconds, - * which will be used if no timeout is provided in the argument options object. - * - * The returned Promise is fulfilled with a result object containing these - * properties: - * - success (boolean): True if this instance is ready to use with a valid - * datafile, or false if this instance failed to become - * ready or was closed prior to becoming ready. - * - reason (string=): If success is false, this is a string property with - * an explanatory message. Failure could be due to - * expiration of the timeout, network errors, - * unsuccessful responses, datafile parse errors, - * datafile validation errors, or the instance being - * closed - * @param {Object=} options - * @param {number|undefined} options.timeout - * @return {Promise} - */ -Optimizely.prototype.onReady = function(options) { - var timeout; - if (typeof options === 'object' && options !== null) { - timeout = options.timeout; - } - if (!isSafeInteger(timeout)) { - timeout = DEFAULT_ONREADY_TIMEOUT; - } - - var resolveTimeoutPromise; - var timeoutPromise = new Promise(function(resolve) { - resolveTimeoutPromise = resolve; - }); - - var timeoutId = this.__nextReadyTimeoutId; - this.__nextReadyTimeoutId++; - - var onReadyTimeout = function() { - delete this.__readyTimeouts[timeoutId]; - resolveTimeoutPromise({ - success: false, - reason: sprintf('onReady timeout expired after %s ms', timeout), - }); - }.bind(this); - var readyTimeout = setTimeout(onReadyTimeout, timeout); - var onClose = function() { - resolveTimeoutPromise({ - success: false, - reason: 'Instance closed', - }); - }; - - this.__readyTimeouts[timeoutId] = { - readyTimeout: readyTimeout, - onClose: onClose, - }; - - this.__readyPromise.then( - function() { - clearTimeout(readyTimeout); - delete this.__readyTimeouts[timeoutId]; - resolveTimeoutPromise({ - success: true, - }); - }.bind(this) - ); - - return Promise.race([this.__readyPromise, timeoutPromise]); -}; - -export default Optimizely; diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts new file mode 100644 index 000000000..175e2a2fa --- /dev/null +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -0,0 +1,1323 @@ +/**************************************************************************** + * Copyright 2016-2020, 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 { sprintf, objectValues } from '@optimizely/js-sdk-utils'; +import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; +import * as eventProcessor from '@optimizely/js-sdk-event-processor'; +import { EventTags, EventDispatcher } from '@optimizely/optimizely-sdk'; +import { FeatureFlag } from '../core/project_config/entities'; +import { UserAttributes, Variation } from '../shared_types'; +import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; +import { ProjectConfigManager, createProjectConfigManager }from '@optimizely/optimizely-sdk/lib/core/projet_config_manager'; + +import { isSafeInteger } from '../utils/fns' +import { validate } from '../utils/attributes_validator'; +// import * as decisionService from '../core/decision_service'; +import { DecisionService, createDecisionService} from '@optimizely/optimizely-sdk/lib/core/decision_service'; +import * as enums from '../utils/enums'; +import { getImpressionEvent, getConversionEvent } from '@optimizely/optimizely-sdk/lib/core/event_builder'; +import { buildImpressionEvent, buildConversionEvent } from '@optimizely/optimizely-sdk/lib/core/event_builder'; +import * as eventTagsValidator from '../utils/event_tags_validator'; +// import notificationCenter from '../core/notification_center'; +import { NotificationCenter, createNotificationCenter} from '@optimizely/optimizely-sdk/lib/core/notification_center'; +// import projectConfig from '../core/project_config'; +import * as projectConfig from '@optimizely/optimizely-sdk/lib/core/project_config'; +import * as userProfileServiceValidator from '../utils/user_profile_service_validator'; +import * as stringValidator from '../utils/string_value_validator'; +// import * as projectConfigManager from '@optimizely/optimizely-sdk/lib/core/projet_config_manager'; + +const ERROR_MESSAGES = enums.ERROR_MESSAGES; +const LOG_LEVEL = enums.LOG_LEVEL; +const LOG_MESSAGES = enums.LOG_MESSAGES; +const MODULE_NAME = 'OPTIMIZELY'; +const DECISION_SOURCES = enums.DECISION_SOURCES; +const FEATURE_VARIABLE_TYPES = enums.FEATURE_VARIABLE_TYPES; +const DECISION_NOTIFICATION_TYPES = enums.DECISION_NOTIFICATION_TYPES; +const NOTIFICATION_TYPES = enums.NOTIFICATION_TYPES; + +const DEFAULT_ONREADY_TIMEOUT = 30000; + +/** + * The Optimizely class + * @param {Object} config + * @param {string} config.clientEngine + * @param {string} config.clientVersion + * @param {Object|string} config.datafile + * @param {Object} config.errorHandler + * @param {Object} config.eventDispatcher + * @param {Object} config.logger + * @param {Object} config.userProfileService + * @param {Object} config.eventBatchSize + * @param {Object} config.eventFlushInterval + * @param {string} config.sdkKey + */ +export default class Optimizely { + private clientEngine: string; + private clientVersion: string; + private errorHandler: ErrorHandler; + private eventDispatcher: EventDispatcher; + private __isOptimizelyConfigValid: boolean; + private logger: LogHandler; + private projectConfigManager: ProjectConfigManager; + private __disposeOnUpdate: () => void; + private notificationCenter: NotificationCenter; + private decisionService: DecisionService; + private eventProcessor: eventProcessor.EventProcessor; + private __readyPromise: any; //TODO + private __readyTimeouts: any;//TODO + private __nextReadyTimeoutId: any;//TODO + + + constructor(config: ProjectConfig) { + let clientEngine = config.clientEngine; + if (enums.VALID_CLIENT_ENGINES.indexOf(clientEngine) === -1) { + config.logger.log( + LOG_LEVEL.INFO, + sprintf(LOG_MESSAGES.INVALID_CLIENT_ENGINE, MODULE_NAME, clientEngine) + ); + clientEngine = enums.NODE_CLIENT_ENGINE; + } + + this.clientEngine = clientEngine; + this.clientVersion = config.clientVersion || enums.NODE_CLIENT_VERSION; + this.errorHandler = config.errorHandler; + this.eventDispatcher = config.eventDispatcher; + this.__isOptimizelyConfigValid = config.isValidInstance; + this.logger = config.logger; + + this.projectConfigManager = createProjectConfigManager({ + datafile: config.datafile, + datafileOptions: config.datafileOptions, + jsonSchemaValidator: config.jsonSchemaValidator, + sdkKey: config.sdkKey, + }); + + this.__disposeOnUpdate = this.projectConfigManager.onUpdate( + function(this: Optimizely, configObj: ProjectConfig) { + this.logger.log( + LOG_LEVEL.INFO, + sprintf(LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, MODULE_NAME, configObj.revision, configObj.projectId) + ); + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + }.bind(this) + ); + + const projectConfigManagerReadyPromise = this.projectConfigManager.onReady(); + + let userProfileService = null; + if (config.userProfileService) { + try { + if (userProfileServiceValidator.validate(config.userProfileService)) { + userProfileService = config.userProfileService; + this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.VALID_USER_PROFILE_SERVICE, MODULE_NAME)); + } + } catch (ex) { + this.logger.log(LOG_LEVEL.WARNING, ex.message); + } + } + + this.decisionService = createDecisionService({ + userProfileService: userProfileService, + logger: this.logger, + UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators, + }); + + this.notificationCenter = createNotificationCenter({ + logger: this.logger, + errorHandler: this.errorHandler, + }); + + this.eventProcessor = new eventProcessor.LogTierV1EventProcessor({ + dispatcher: this.eventDispatcher, + flushInterval: config.eventFlushInterval, + batchSize: config.eventBatchSize, + maxQueueSize: config.eventMaxQueueSize, // TODO: update event-processor to include maxQueueSize + notificationCenter: this.notificationCenter, + }); + + + const eventProcessorStartedPromise = this.eventProcessor.start(); + + this.__readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then(function(promiseResults) { + // Only return status from project config promise because event processor promise does not return any status. + return promiseResults[0]; + }) + + this.__readyTimeouts = {}; + this.__nextReadyTimeoutId = 0; + } + + /** + * Returns a truthy value if this instance currently has a valid project config + * object, and the initial configuration object that was passed into the + * constructor was also valid. + * @return {*} + */ + __isValidInstance(): boolean { + return this.__isOptimizelyConfigValid && !!this.projectConfigManager.getConfig(); + }; + + /** + * Buckets visitor and sends impression event to Optimizely. + * @param {string} experimentKey + * @param {string} userId + * @param {UserAttributes} attributes + * @return {string|null} variation key + */ + activate(experimentKey: string, userId: string, attributes: UserAttributes): string | null { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'activate')); + return null; + } + + if (!this.__validateInputs({ experiment_key: experimentKey, user_id: userId }, attributes)) { + return this.__notActivatingExperiment(experimentKey, userId); + } + + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return null; + } + + try { + var variationKey = this.getVariation(experimentKey, userId, attributes); + if (variationKey === null) { + return this.__notActivatingExperiment(experimentKey, userId); + } + + // If experiment is not set to 'Running' status, log accordingly and return variation key + if (!projectConfig.isRunning(configObj, experimentKey)) { + var shouldNotDispatchActivateLogMessage = sprintf( + LOG_MESSAGES.SHOULD_NOT_DISPATCH_ACTIVATE, + MODULE_NAME, + experimentKey + ); + this.logger.log(LOG_LEVEL.DEBUG, shouldNotDispatchActivateLogMessage); + return variationKey; + } + + this._sendImpressionEvent(experimentKey, variationKey, userId, attributes); + + return variationKey; + } catch (ex) { + this.logger.log(LOG_LEVEL.ERROR, ex.message); + var failedActivationLogMessage = sprintf( + LOG_MESSAGES.NOT_ACTIVATING_USER, + MODULE_NAME, + userId, + experimentKey + ); + this.logger.log(LOG_LEVEL.INFO, failedActivationLogMessage); + this.errorHandler.handleError(ex); + return null; + } + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Create an impression event and call the event dispatcher's dispatch method to + * send this event to Optimizely. Then use the notification center to trigger + * any notification listeners for the ACTIVATE notification type. + * @param {string} experimentKey Key of experiment that was activated + * @param {string} variationKey Key of variation shown in experiment that was activated + * @param {string} userId ID of user to whom the variation was shown + * @param {UserAttributes} attributes Optional user attributes + */ + _sendImpressionEvent(experimentKey: string, variationKey: string, userId: string, attributes?: UserAttributes): void { + const configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return; + } + + const impressionEvent = buildImpressionEvent({ + experimentKey: experimentKey, + variationKey: variationKey, + userId: userId, + userAttributes: attributes, + clientEngine: this.clientEngine, + clientVersion: this.clientVersion, + configObj: configObj, + }); + // TODO is it okay to not pass a projectConfig as second argument + this.eventProcessor.process(impressionEvent); + this.__emitNotificationCenterActivate(experimentKey, variationKey, userId, attributes); + }; + + /** + * Emit the ACTIVATE notification on the notificationCenter + * @param {string} experimentKey Key of experiment that was activated + * @param {string} variationKey Key of variation shown in experiment that was activated + * @param {string} userId ID of user to whom the variation was shown + * @param {UserAttributes} attributes Optional user attributes + */ + __emitNotificationCenterActivate(experimentKey: string, variationKey: string, userId: string, attributes?: UserAttributes) { + const configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return; + } + + const variationId = projectConfig.getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey); + const experimentId = projectConfig.getExperimentId(configObj, experimentKey); + const impressionEventOptions = { + attributes: attributes, + clientEngine: this.clientEngine, + clientVersion: this.clientVersion, + configObj: configObj, + experimentId: experimentId, + userId: userId, + variationId: variationId, + logger: this.logger, + }; + const impressionEvent = getImpressionEvent(impressionEventOptions); + const experiment = configObj.experimentKeyMap[experimentKey]; + let variation; + if (experiment && experiment.variationKeyMap) { + variation = experiment.variationKeyMap[variationKey]; + } + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, { + experiment: experiment, + userId: userId, + attributes: attributes, + variation: variation, + logEvent: impressionEvent, + }); + }; + + /** + * Sends conversion event to Optimizely. + * @param {string} eventKey + * @param {string} userId + * @param {UserAttributes} attributes + * @param {EventTags} eventTags Values associated with the event. + */ + track(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'track')); + return; + } + + if (!this.__validateInputs({ user_id: userId, event_key: eventKey }, attributes, eventTags)) { + return; + } + + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return; + } + + if (!projectConfig.eventWithKeyExists(configObj, eventKey)) { + this.logger.log( + LOG_LEVEL.WARNING, + sprintf(enums.LOG_MESSAGES.EVENT_KEY_NOT_FOUND, MODULE_NAME, eventKey) + ); + this.logger.log(LOG_LEVEL.WARNING, sprintf(LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId)); + return; + } + + // remove null values from eventTags + eventTags = this.__filterEmptyValues(eventTags as EventTags); + var conversionEvent = buildConversionEvent({ + eventKey: eventKey, + eventTags: eventTags, + userId: userId, + userAttributes: attributes, + clientEngine: this.clientEngine, + clientVersion: this.clientVersion, + configObj: configObj, + }); + this.logger.log(LOG_LEVEL.INFO, sprintf(enums.LOG_MESSAGES.TRACK_EVENT, MODULE_NAME, eventKey, userId)); + // TODO is it okay to not pass a projectConfig as second argument + this.eventProcessor.process(conversionEvent); + this.__emitNotificationCenterTrack(eventKey, userId, attributes as UserAttributes, eventTags); + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + var failedTrackLogMessage = sprintf(LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); + this.logger.log(LOG_LEVEL.ERROR, failedTrackLogMessage); + } + } + /** + * Send TRACK event to notificationCenter + * @param {string} eventKey + * @param {string} userId + * @param {UserAttributes} attributes + * @param {EventTags} eventTags Values associated with the event. + */ + __emitNotificationCenterTrack(eventKey: string, userId: string, attributes: UserAttributes, eventTags: EventTags): void { + try { + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return; + } + + var conversionEventOptions = { + attributes: attributes, + clientEngine: this.clientEngine, + clientVersion: this.clientVersion, + configObj: configObj, + eventKey: eventKey, + eventTags: eventTags, + logger: this.logger, + userId: userId, + }; + var conversionEvent = getConversionEvent(conversionEventOptions); + + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.TRACK, { + eventKey: eventKey, + userId: userId, + attributes: attributes, + eventTags: eventTags, + logEvent: conversionEvent, + }); + } catch (ex) { + this.logger.log(LOG_LEVEL.ERROR, ex.message); + this.errorHandler.handleError(ex); + } + }; + + /** + * Gets variation where visitor will be bucketed. + * @param {string} experimentKey + * @param {string} userId + * @param {UserAttributes} attributes + * @return {string|null} variation key + */ + getVariation(experimentKey: string, userId: string, attributes: UserAttributes): string | null { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getVariation')); + return null; + } + + try { + if (!this.__validateInputs({ experiment_key: experimentKey, user_id: userId }, attributes)) { + return null; + } + + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return null; + } + + var experiment = configObj.experimentKeyMap[experimentKey]; + if (!experiment) { + this.logger.log( + LOG_LEVEL.DEBUG, + sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey) + ); + return null; + } + + var variationKey = this.decisionService.getVariation(configObj, experimentKey, userId, attributes); + var decisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) + ? DECISION_NOTIFICATION_TYPES.FEATURE_TEST + : DECISION_NOTIFICATION_TYPES.AB_TEST; + + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { + type: decisionNotificationType, + userId: userId, + attributes: attributes || {}, + decisionInfo: { + experimentKey: experimentKey, + variationKey: variationKey, + }, + }); + + return variationKey; + } catch (ex) { + this.logger.log(LOG_LEVEL.ERROR, ex.message); + this.errorHandler.handleError(ex); + return null; + } + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Force a user into a variation for a given experiment. + * @param {string} experimentKey + * @param {string} userId + * @param {string|null} variationKey user will be forced into. If null, then clear the existing experiment-to-variation mapping. + * @return {boolean} A boolean value that indicates if the set completed successfully. + */ + setForcedVariation(experimentKey: string, userId: string, variationKey: string | null): boolean { + if (!this.__validateInputs({ experiment_key: experimentKey, user_id: userId })) { + return false; + } + + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return false; + } + + try { + return this.decisionService.setForcedVariation(configObj, experimentKey, userId, variationKey); + } catch (ex) { + this.logger.log(LOG_LEVEL.ERROR, ex.message); + this.errorHandler.handleError(ex); + return false; + } + }; + + /** + * Gets the forced variation for a given user and experiment. + * @param {string} experimentKey + * @param {string} userId + * @return {string|null} The forced variation key. + */ + getForcedVariation(experimentKey: string, userId: string): string | null { + if (!this.__validateInputs({ experiment_key: experimentKey, user_id: userId })) { + return null; + } + + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return null; + } + + try { + return this.decisionService.getForcedVariation(configObj, experimentKey, userId); + } catch (ex) { + this.logger.log(LOG_LEVEL.ERROR, ex.message); + this.errorHandler.handleError(ex); + return null; + } + }; + + /** + * Validate string inputs, user attributes and event tags. + * @param {unknown} stringInputs Map of string keys and associated values + * @param {unknown} userAttributes Optional parameter for user's attributes + * @param {unknown} eventTags Optional parameter for event tags + * @return {boolean} True if inputs are valid + * + */ + __validateInputs( + stringInputs?: unknown, + userAttributes?: unknown, + eventTags?: unknown + ): boolean { + try { + // Null, undefined or non-string user Id is invalid. + if (typeof stringInputs === 'object' && stringInputs !== null) { + if (stringInputs.hasOwnProperty('user_id')) { + const userId = stringInputs['user_id']; + if (typeof userId !== 'string' || userId === null || userId === 'undefined') { + throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, 'user_id')); + } + + delete stringInputs['user_id']; + } + const inputKeys = Object.keys(stringInputs); + for (var index = 0; index < inputKeys.length; index++) { + var key = inputKeys[index]; + if (!stringValidator.validate(stringInputs[key])) { + throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, key)); + } + } + } + if (userAttributes) { + validate(userAttributes); + } + if (eventTags) { + eventTagsValidator.validate(eventTags); + } + return true; + + } catch (ex) { + this.logger.log(LOG_LEVEL.ERROR, ex.message); + this.errorHandler.handleError(ex); + return false; + } + + }; + + /** + * Shows failed activation log message and returns null when user is not activated in experiment + * @param {string} experimentKey + * @param {string} userId + * @return {null} + */ + __notActivatingExperiment(experimentKey: string, userId: string): null { + const failedActivationLogMessage = sprintf( + LOG_MESSAGES.NOT_ACTIVATING_USER, + MODULE_NAME, + userId, + experimentKey + ); + this.logger.log(LOG_LEVEL.INFO, failedActivationLogMessage); + return null; + }; + + /** + * Filters out attributes/eventTags with null or undefined values + * @param {EventTags} map + * @returns {EventTags} + */ + __filterEmptyValues(map: EventTags): EventTags { + for (var key in map) { + if (map.hasOwnProperty(key) && (map[key] === null || map[key] === undefined)) { + delete map[key]; + } + } + return map; + }; + + /** + * Returns true if the feature is enabled for the given user. + * @param {string} featureKey Key of feature which will be checked + * @param {string} userId ID of user which will be checked + * @param {UserAttributes} attributes Optional user attributes + * @return {boolean} true if the feature is enabled for the user, false otherwise + */ + isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean { + try { + if (!this.__isValidInstance()) { + this.logger.log( + LOG_LEVEL.ERROR, + sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'isFeatureEnabled') + ); + return false; + } + + if (!this.__validateInputs({ feature_key: featureKey, user_id: userId }, attributes)) { + return false; + } + + const configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return false; + } + + const feature = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); + if (!feature) { + return false; + } + + let sourceInfo = {}; + let featureEnabled = false; + const decision = this.decisionService.getVariationForFeature(configObj, feature, userId, attributes); + const variation = decision.variation; + + if (variation) { + featureEnabled = variation.featureEnabled; + if ( + decision.decisionSource === DECISION_SOURCES.FEATURE_TEST && + decision.experiment !== null && + decision.variation !== null + ) { + sourceInfo = { + experimentKey: decision.experiment.key, + variationKey: decision.variation.key, + }; + // got a variation from the exp, so we track the impression + this._sendImpressionEvent(decision.experiment.key, decision.variation.key, userId, attributes); + } + } + + if (featureEnabled === true) { + this.logger.log( + LOG_LEVEL.INFO, + sprintf(LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId) + ); + } else { + this.logger.log( + LOG_LEVEL.INFO, + sprintf(LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, MODULE_NAME, featureKey, userId) + ); + featureEnabled = false; + } + + const featureInfo = { + featureKey: featureKey, + featureEnabled: featureEnabled, + source: decision.decisionSource, + sourceInfo: sourceInfo, + }; + + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { + type: DECISION_NOTIFICATION_TYPES.FEATURE, + userId: userId, + attributes: attributes || {}, + decisionInfo: featureInfo, + }); + + return featureEnabled; + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return false; + } + }; + + /** + * Returns an Array containing the keys of all features in the project that are + * enabled for the given user. + * @param {string} userId + * @param {UserAttributes} attributes + * @return {string[]} Array of feature keys (strings) + */ + getEnabledFeatures(userId: string, attributes: UserAttributes): string[] { + try { + const enabledFeatures: string[] = []; + if (!this.__isValidInstance()) { + this.logger.log( + LOG_LEVEL.ERROR, + sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getEnabledFeatures') + ); + return enabledFeatures; + } + + if (!this.__validateInputs({ user_id: userId })) { + return enabledFeatures; + } + + const configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return enabledFeatures; + } + if (configObj.featureKeyMap) { + objectValues(configObj.featureKeyMap).forEach( + function(this: Optimizely,feature: FeatureFlag): void { + if (this.isFeatureEnabled(feature.key, userId, attributes)) { + enabledFeatures.push(feature.key); + } + }.bind(this) + ); + } + + return enabledFeatures; + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return []; + } + }; + + /** + * Returns dynamically-typed value of the variable attached to the given + * feature flag. Returns null if the feature key or variable key is invalid. + * + * @param {string} featureKey Key of the feature whose variable's + * value is being accessed + * @param {string} variableKey Key of the variable whose value is + * being accessed + * @param {string} userId ID for the user + * @param {UserAttributes} attributes Optional user attributes + * @return {unknown} Value of the variable cast to the appropriate + * type, or null if the feature key is invalid or the variable key is invalid + */ + + getFeatureVariable( + featureKey: string, + variableKey: string, + userId: string, + attributes?: UserAttributes + ): unknown { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariable')); + return null; + } + return this._getFeatureVariableForType(featureKey, variableKey, null, userId, attributes); + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Helper method to get the value for a variable of a certain type attached to a + * feature flag. Returns null if the feature key is invalid, the variable key is + * invalid, the given variable type does not match the variable's actual type, + * or the variable value cannot be cast to the required type. If the given variable + * type is null, the value of the variable cast to the appropriate type is returned. + * + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string|null} variableType Type of the variable whose value is being + * accessed (must be one of FEATURE_VARIABLE_TYPES + * in lib/utils/enums/index.js), or null to return the + * value of the variable cast to the appropriate type + * @param {string} userId ID for the user + * @param {UserAttributes} attributes Optional user attributes + * @return {unknown} Value of the variable cast to the appropriate + * type, or null if the feature key is invalid, thevariable key is invalid, or there is + * a mismatch with the type of the variable + */ + _getFeatureVariableForType( + featureKey: string, + variableKey: string, + variableType: string | null, + userId: string, + attributes: UserAttributes) { + if (!this.__validateInputs({ feature_key: featureKey, variable_key: variableKey, user_id: userId }, attributes)) { + return null; + } + + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return null; + } + + var featureFlag = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); + if (!featureFlag) { + return null; + } + + var variable = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, this.logger); + if (!variable) { + return null; + } + + if (variableType && variable.type !== variableType) { + this.logger.log( + LOG_LEVEL.WARNING, + sprintf(LOG_MESSAGES.VARIABLE_REQUESTED_WITH_WRONG_TYPE, MODULE_NAME, variableType, variable.type) + ); + return null; + } + + var decision = this.decisionService.getVariationForFeature(configObj, featureFlag, userId, attributes); + var featureEnabled = decision.variation !== null ? decision.variation.featureEnabled : false; + var variableValue = this._getFeatureVariableValueFromVariation(featureKey, featureEnabled, decision.variation, variable, userId); + var sourceInfo = {}; + if ( + decision.decisionSource === DECISION_SOURCES.FEATURE_TEST && + decision.experiment !== null && + decision.variation !== null + ) { + sourceInfo = { + experimentKey: decision.experiment.key, + variationKey: decision.variation.key, + }; + } + + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: userId, + attributes: attributes || {}, + decisionInfo: { + featureKey: featureKey, + featureEnabled: featureEnabled, + source: decision.decisionSource, + variableKey: variableKey, + variableValue: variableValue, + variableType: variable.type, + sourceInfo: sourceInfo, + }, + }); + return variableValue; + }; + + /** + * Helper method to get the non type-casted value for a variable attached to a + * feature flag. Returns appropriate variable value depending on whether there + * was a matching variation, feature was enabled or not or varible was part of the + * available variation or not. Also logs the appropriate message explaining how it + * evaluated the value of the variable. + * + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {boolean} featureEnabled Boolean indicating if feature is enabled or not + * @param {object} variation variation returned by decision service + * @param {object} variable varible whose value is being evaluated + * @param {string} userId ID for the user + * @return {string|null} String value of the variable or null if the config Obj + * is null + */ + _getFeatureVariableValueFromVariation( + featureKey: string, + featureEnabled: boolean, + variation: Variation, + variable: , userId) { + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return null; + } + + var variableValue = variable.defaultValue; + if (variation !== null) { + var value = projectConfig.getVariableValueForVariation(configObj, variable, variation, this.logger); + if (value !== null) { + if (featureEnabled) { + variableValue = value; + this.logger.log( + LOG_LEVEL.INFO, + sprintf( + LOG_MESSAGES.USER_RECEIVED_VARIABLE_VALUE, + MODULE_NAME, + variableValue, + variable.key, + featureKey + ) + ); + } else { + this.logger.log( + LOG_LEVEL.INFO, + sprintf( + LOG_MESSAGES.FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, + MODULE_NAME, + featureKey, + userId, + variableValue + ) + ); + } + } else { + this.logger.log( + LOG_LEVEL.INFO, + sprintf( + LOG_MESSAGES.VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, + MODULE_NAME, + variable.key, + variation.key + ) + ); + } + } else { + this.logger.log( + LOG_LEVEL.INFO, + sprintf( + LOG_MESSAGES.USER_RECEIVED_DEFAULT_VARIABLE_VALUE, + MODULE_NAME, + userId, + variable.key, + featureKey + ) + ); + } + + return projectConfig.getTypeCastValue(variableValue, variable.type, this.logger); + } + + /** + * Returns value for the given boolean variable attached to the given feature + * flag. + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {Object} attributes Optional user attributes + * @return {boolean|null} Boolean value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable + */ + getFeatureVariableBoolean(featureKey, variableKey, userId, attributes) { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableBoolean')); + return null; + } + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.BOOLEAN, userId, attributes); + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Returns value for the given double variable attached to the given feature + * flag. + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {Object} attributes Optional user attributes + * @return {number|null} Number value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable + */ + getFeatureVariableDouble(featureKey, variableKey, userId, attributes) { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableDouble')); + return null; + } + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.DOUBLE, userId, attributes); + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Returns value for the given integer variable attached to the given feature + * flag. + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {Object} attributes Optional user attributes + * @return {number|null} Number value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable + */ + getFeatureVariableInteger(featureKey, variableKey, userId, attributes) { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableInteger')); + return null; + } + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.INTEGER, userId, attributes); + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Returns value for the given string variable attached to the given feature + * flag. + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {Object} attributes Optional user attributes + * @return {string|null} String value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable + */ + getFeatureVariableString(featureKey, variableKey, userId, attributes) { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString')); + return null; + } + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.STRING, userId, attributes); + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Returns value for the given json variable attached to the given feature + * flag. + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {Object} attributes Optional user attributes + * @return {object|null} Object value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable + */ + getFeatureVariableJSON(featureKey, variableKey, userId, attributes) { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableJSON')); + return null; + } + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.JSON, userId, attributes); + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Returns values for all the variables attached to the given feature + * flag. + * @param {string} featureKey Key of the feature whose variables are being + * accessed + * @param {string} userId ID for the user + * @param {Object} attributes Optional user attributes + * @return {object|null} Object containing all the variables, or null if the + * feature key is invalid + */ + getAllFeatureVariables(featureKey, userId, attributes) { + try { + if (!this.__isValidInstance()) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getAllFeatureVariables')); + return null; + } + + if (!this.__validateInputs({ feature_key: featureKey, user_id: userId }, attributes)) { + return null; + } + + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return null; + } + + var featureFlag = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); + if (!featureFlag) { + return null; + } + + var decision = this.decisionService.getVariationForFeature(configObj, featureFlag, userId, attributes); + var featureEnabled = decision.variation !== null ? decision.variation.featureEnabled : false; + var allVariables = {}; + + featureFlag.variables.forEach(function (variable) { + allVariables[variable.key] = this._getFeatureVariableValueFromVariation(featureKey, featureEnabled, decision.variation, variable, userId); + }.bind(this)); + + var sourceInfo = {}; + if (decision.decisionSource === DECISION_SOURCES.FEATURE_TEST) { + sourceInfo = { + experimentKey: decision.experiment.key, + variationKey: decision.variation.key, + }; + } + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { + type: DECISION_NOTIFICATION_TYPES.ALL_FEATURE_VARIABLES, + userId: userId, + attributes: attributes || {}, + decisionInfo: { + featureKey: featureKey, + featureEnabled: featureEnabled, + source: decision.decisionSource, + variableValues: allVariables, + sourceInfo: sourceInfo, + }, + }); + + return allVariables; + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Returns OptimizelyConfig object containing experiments and features data + * @return {Object} + * + * OptimizelyConfig Object Schema + * { + * 'experimentsMap': { + * 'my-fist-experiment': { + * 'id': '111111', + * 'key': 'my-fist-experiment' + * 'variationsMap': { + * 'variation_1': { + * 'id': '121212', + * 'key': 'variation_1', + * 'variablesMap': { + * 'age': { + * 'id': '222222', + * 'key': 'age', + * 'type': 'integer', + * 'value': '0', + * } + * } + * } + * } + * } + * }, + * 'featuresMap': { + * 'awesome-feature': { + * 'id': '333333', + * 'key': 'awesome-feature', + * 'experimentsMap': Object, + * 'variationsMap': Object, + * } + * } + * } + */ + getOptimizelyConfig() { + try { + var configObj = this.projectConfigManager.getConfig(); + if (!configObj) { + return null; + } + return this.projectConfigManager.getOptimizelyConfig(); + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } + }; + + /** + * Stop background processes belonging to this instance, including: + * + * - Active datafile requests + * - Pending datafile requests + * - Pending event queue flushes + * + * In-flight datafile requests will be aborted. Any events waiting to be sent + * as part of a batched event request will be immediately flushed to the event + * dispatcher. + * + * Returns a Promise that fulfills after all in-flight event dispatcher requests + * (including any final request resulting from flushing the queue as described + * above) are complete. If there are no in-flight event dispatcher requests and + * no queued events waiting to be sent, returns an immediately-fulfilled Promise. + * + * Returned Promises are fulfilled with result objects containing these + * properties: + * - success (boolean): true if the event dispatcher signaled completion of + * all in-flight and final requests, or if there were no + * queued events and no in-flight requests. false if an + * unexpected error was encountered during the close + * process. + * - reason (string=): If success is false, this is a string property with + * an explanatory message. + * + * NOTE: After close is called, this instance is no longer usable - any events + * generated will no longer be sent to the event dispatcher. + * + * @return {Promise} + */ + close() { + try { + var eventProcessorStoppedPromise = this.eventProcessor.stop(); + if (this.__disposeOnUpdate) { + this.__disposeOnUpdate(); + this.__disposeOnUpdate = null; + } + if (this.projectConfigManager) { + this.projectConfigManager.stop(); + } + Object.keys(this.__readyTimeouts).forEach( + function(readyTimeoutId) { + var readyTimeoutRecord = this.__readyTimeouts[readyTimeoutId]; + clearTimeout(readyTimeoutRecord.readyTimeout); + readyTimeoutRecord.onClose(); + }.bind(this) + ); + this.__readyTimeouts = {}; + return eventProcessorStoppedPromise.then( + function() { + return { + success: true, + }; + }, + function(err) { + return { + success: false, + reason: String(err), + }; + } + ); + } catch (err) { + this.logger.log(LOG_LEVEL.ERROR, err.message); + this.errorHandler.handleError(err); + return Promise.resolve({ + success: false, + reason: String(err), + }); + } + }; + + /** + * Returns a Promise that fulfills when this instance is ready to use (meaning + * it has a valid datafile), or has failed to become ready within a period of + * time (configurable by the timeout property of the options argument), or when + * this instance is closed via the close method. + * + * If a valid datafile was provided in the constructor, the returned Promise is + * immediately fulfilled. If an sdkKey was provided, a manager will be used to + * fetch a datafile, and the returned promise will fulfill if that fetch + * succeeds or fails before the timeout. The default timeout is 30 seconds, + * which will be used if no timeout is provided in the argument options object. + * + * The returned Promise is fulfilled with a result object containing these + * properties: + * - success (boolean): True if this instance is ready to use with a valid + * datafile, or false if this instance failed to become + * ready or was closed prior to becoming ready. + * - reason (string=): If success is false, this is a string property with + * an explanatory message. Failure could be due to + * expiration of the timeout, network errors, + * unsuccessful responses, datafile parse errors, + * datafile validation errors, or the instance being + * closed + * @param {Object=} options + * @param {number|undefined} options.timeout + * @return {Promise} + */ + onReady(options) { + var timeout; + if (typeof options === 'object' && options !== null) { + timeout = options.timeout; + } + if (!isSafeInteger(timeout)) { + timeout = DEFAULT_ONREADY_TIMEOUT; + } + + var resolveTimeoutPromise; + var timeoutPromise = new Promise(function(resolve) { + resolveTimeoutPromise = resolve; + }); + + var timeoutId = this.__nextReadyTimeoutId; + this.__nextReadyTimeoutId++; + + var onReadyTimeout = function() { + delete this.__readyTimeouts[timeoutId]; + resolveTimeoutPromise({ + success: false, + reason: sprintf('onReady timeout expired after %s ms', timeout), + }); + }.bind(this); + var readyTimeout = setTimeout(onReadyTimeout, timeout); + var onClose = function() { + resolveTimeoutPromise({ + success: false, + reason: 'Instance closed', + }); + }; + + this.__readyTimeouts[timeoutId] = { + readyTimeout: readyTimeout, + onClose: onClose, + }; + + this.__readyPromise.then( + function() { + clearTimeout(readyTimeout); + delete this.__readyTimeouts[timeoutId]; + resolveTimeoutPromise({ + success: true, + }); + }.bind(this) + ); + + return Promise.race([this.__readyPromise, timeoutPromise]); + }; +} diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index 11ac8732e..0ff474c87 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -4,9 +4,16 @@ export type UserAttributes = { [name: string]: any; } +export interface VariationVariable { + id: string; + value: string; +} + export interface Variation { id: string; key: string; + featureEnabled: boolean; + variables: VariationVariable[]; } export interface Experiment { @@ -23,6 +30,7 @@ export interface Experiment { // TODO[OASIS-6649]: Don't use object type // eslint-disable-next-line @typescript-eslint/ban-types forcedVariations: object; + variationKeyMap?: {[key: string]: Variation} } // Information about past bucketing decisions for a user. From e6eb70fee2e1f1ade0acf282e48a625cd3cfeb73 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 25 Sep 2020 10:21:28 -0700 Subject: [PATCH 12/22] Change ProjectConfigManager method to create ProjectConfigManager to createProjectConfigManager for clarity and update unit tests --- .../project_config/project_config_manager.js | 8 +++-- .../project_config_manager.tests.js | 36 +++++++++---------- .../lib/optimizely/index.tests.js | 14 ++++---- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.js b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.js index 7e32cd766..39c66b12a 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.js +++ b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.js @@ -51,7 +51,7 @@ function getErrorMessage(maybeError, defaultMessage) { * @param {Object=} config.jsonSchemaValidator * @param {string=} config.sdkKey */ -export function ProjectConfigManager(config) { +function ProjectConfigManager(config) { try { this.__initialize(config); } catch (ex) { @@ -282,6 +282,10 @@ ProjectConfigManager.prototype.stop = function() { this.__updateListeners = []; }; +export var createProjectConfigManager = function(config) { + return new ProjectConfigManager(config); +} + export default { - ProjectConfigManager: ProjectConfigManager, + createProjectConfigManager: createProjectConfigManager, }; diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js index 24e4752bf..359a94599 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js @@ -56,7 +56,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); it('should call the error handler and fulfill onReady with an unsuccessful result if neither datafile nor sdkKey are passed into the constructor', function() { - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ }); sinon.assert.calledOnce(globalStubErrorHandler.handleError); var errorMessage = globalStubErrorHandler.handleError.lastCall.args[0].message; @@ -70,7 +70,7 @@ describe('lib/core/project_config/project_config_manager', function() { it('should call the error handler and fulfill onReady with an unsuccessful result if the datafile JSON is malformed', function() { var invalidDatafileJSON = 'abc'; - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: invalidDatafileJSON, }); sinon.assert.calledOnce(globalStubErrorHandler.handleError); @@ -86,7 +86,7 @@ describe('lib/core/project_config/project_config_manager', function() { it('should call the error handler and fulfill onReady with an unsuccessful result if the datafile is not valid', function() { var invalidDatafile = testData.getTestProjectConfig(); delete invalidDatafile['projectId']; - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: invalidDatafile, jsonSchemaValidator: jsonSchemaValidator, }); @@ -104,7 +104,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); it('should call the error handler and fulfill onReady with an unsuccessful result if the datafile version is not supported', function() { - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: testData.getUnsupportedVersionConfig(), jsonSchemaValidator: jsonSchemaValidator, }); @@ -128,7 +128,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); it('should skip JSON schema validation if jsonSchemaValidator is not provided', function() { - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: testData.getTestProjectConfig(), }); sinon.assert.notCalled(jsonSchemaValidator.validate); @@ -136,7 +136,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); it('should not skip JSON schema validation if jsonSchemaValidator is provided', function() { - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: testData.getTestProjectConfig(), jsonSchemaValidator: jsonSchemaValidator, }); @@ -151,7 +151,7 @@ describe('lib/core/project_config/project_config_manager', function() { it('should return a valid datafile from getConfig and resolve onReady with a successful result', function() { var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: cloneDeep(configWithFeatures), }); assert.deepEqual(manager.getConfig(), projectConfig.createProjectConfig(configWithFeatures)); @@ -164,7 +164,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() { var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: configWithFeatures, }); var onUpdateSpy = sinon.spy(); @@ -177,7 +177,7 @@ describe('lib/core/project_config/project_config_manager', function() { describe('with a datafile manager', function() { it('passes the correct options to datafile manager', function() { var config = testData.getTestProjectConfig() - new projectConfigManager.ProjectConfigManager({ + projectConfigManager.createProjectConfigManager({ datafile: config, sdkKey: '12345', datafileOptions: { @@ -207,7 +207,7 @@ describe('lib/core/project_config/project_config_manager', function() { on: sinon.stub().returns(function() {}), onReady: sinon.stub().returns(Promise.resolve()), }); - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ sdkKey: '12345', }); assert.isNull(manager.getConfig()); @@ -245,7 +245,7 @@ describe('lib/core/project_config/project_config_manager', function() { on: sinon.stub().returns(function() {}), onReady: sinon.stub().returns(Promise.resolve()), }); - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ sdkKey: '12345', }); var onUpdateSpy = sinon.spy(); @@ -272,7 +272,7 @@ describe('lib/core/project_config/project_config_manager', function() { on: sinon.stub().returns(function() {}), onReady: sinon.stub().returns(Promise.resolve()), }); - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ sdkKey: '12345', }); return manager.onReady().then(function() { @@ -310,7 +310,7 @@ describe('lib/core/project_config/project_config_manager', function() { on: sinon.stub().returns(function() {}), onReady: sinon.stub().returns(Promise.resolve()), }); - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ jsonSchemaValidator: jsonSchemaValidator, sdkKey: '12345', }); @@ -329,7 +329,7 @@ describe('lib/core/project_config/project_config_manager', function() { on: sinon.stub().returns(function() {}), onReady: sinon.stub().returns(Promise.reject(new Error('Failed to become ready'))), }); - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ jsonSchemaValidator: jsonSchemaValidator, sdkKey: '12345', }); @@ -341,7 +341,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); it('calls stop on its datafile manager when its stop method is called', function() { - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ sdkKey: '12345', }); manager.stop(); @@ -359,7 +359,7 @@ describe('lib/core/project_config/project_config_manager', function() { onReady: sinon.stub().returns(Promise.resolve()), }); var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: configWithFeatures, sdkKey: '12345', }); @@ -386,7 +386,7 @@ describe('lib/core/project_config/project_config_manager', function() { onReady: sinon.stub().returns(Promise.resolve()), }); var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: JSON.stringify(configWithFeatures), sdkKey: '12345', }); @@ -413,7 +413,7 @@ describe('lib/core/project_config/project_config_manager', function() { }); it('should return the same config until revision is changed', function() { - var manager = new projectConfigManager.ProjectConfigManager({ + var manager = projectConfigManager.createProjectConfigManager({ datafile: testData.getTestProjectConfig(), sdkKey: '12345', }); diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index c96747596..69d36c572 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -57,7 +57,7 @@ describe('lib/optimizely', function() { handleError: sinon.stub(), }; logging.setErrorHandler(globalStubErrorHandler); - ProjectConfigManagerStub = sinon.stub(projectConfigManager, 'ProjectConfigManager').callsFake(function(config) { + ProjectConfigManagerStub = sinon.stub(projectConfigManager, 'createProjectConfigManager').callsFake(function(config) { var currentConfig = config.datafile ? projectConfig.createProjectConfig(config.datafile) : null; return { stop: sinon.stub(), @@ -237,8 +237,8 @@ describe('lib/optimizely', function() { logger: createdLogger, sdkKey: '12345', }); - sinon.assert.calledOnce(projectConfigManager.ProjectConfigManager); - sinon.assert.calledWithExactly(projectConfigManager.ProjectConfigManager, { + sinon.assert.calledOnce(projectConfigManager.createProjectConfigManager); + sinon.assert.calledWithExactly(projectConfigManager.createProjectConfigManager, { datafile: config, datafileOptions: { autoUpdate: true, @@ -7717,7 +7717,7 @@ describe('lib/optimizely', function() { isValidInstance: true, }); optlyInstance.close(); - var fakeManager = projectConfigManager.ProjectConfigManager.getCall(0).returnValue; + var fakeManager = projectConfigManager.createProjectConfigManager.getCall(0).returnValue; sinon.assert.calledOnce(fakeManager.stop); }); @@ -7771,7 +7771,7 @@ describe('lib/optimizely', function() { }); it('fulfills the promise with the value from the project config manager ready promise after the project config manager ready promise is fulfilled', function() { - projectConfigManager.ProjectConfigManager.callsFake(function(config) { + projectConfigManager.createProjectConfigManager.callsFake(function(config) { var currentConfig = config.datafile ? projectConfig.createProjectConfig(config.datafile) : null; return { stop: sinon.stub(), @@ -7879,7 +7879,7 @@ describe('lib/optimizely', function() { }); it('clears the timeout when the project config manager ready promise fulfills', function() { - projectConfigManager.ProjectConfigManager.callsFake(function(config) { + projectConfigManager.createProjectConfigManager.callsFake(function(config) { return { stop: sinon.stub(), getConfig: sinon.stub().returns(null), @@ -7914,7 +7914,7 @@ describe('lib/optimizely', function() { onUpdate: sinon.stub().returns(function() {}), onReady: sinon.stub().returns({ then: function() {} }), }; - projectConfigManager.ProjectConfigManager.returns(fakeProjectConfigManager); + projectConfigManager.createProjectConfigManager.returns(fakeProjectConfigManager); optlyInstance = new Optimizely({ clientEngine: 'node-sdk', From 95557108aa1bd676a8287eedd90947e77598ff86 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 25 Sep 2020 14:15:58 -0700 Subject: [PATCH 13/22] Convert more methods --- .../lib/core/project_config/index.d.ts | 19 ++ packages/optimizely-sdk/lib/index.d.ts | 2 +- .../optimizely-sdk/lib/optimizely/index.ts | 184 ++++++++++-------- 3 files changed, 127 insertions(+), 78 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/project_config/index.d.ts b/packages/optimizely-sdk/lib/core/project_config/index.d.ts index 0119aaa7f..ef6c50a66 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.d.ts +++ b/packages/optimizely-sdk/lib/core/project_config/index.d.ts @@ -133,4 +133,23 @@ declare module '@optimizely/optimizely-sdk/lib/core/project_config' { * null if the type cast failed */ export function getTypeCastValue(variableValue: string, type: string, logger: LogHandler): T; + + /** + * Get the value of the given variable for the given variation. If the given + * variable has no value for the given variation, return null. Log an error message if the variation is invalid. If the + * variable or variation are invalid, return null. + * @param {ProjectConfig} projectConfig + * @param {FeatureVariable} variable + * @param {Variation} variation + * @param {LogHandler} logger + * @return {string|null} The value of the given variable for the given + * variation, or null if the given variable has no value + * for the given variation or if the variation or variable are invalid + */ + export function getVariableValueForVariation( + projectConfig: ProjectConfig, + variable: import('./entities').FeatureVariable, + variation: import('../../shared_types').Variation, + logger: LogHandler + ): string | null; } diff --git a/packages/optimizely-sdk/lib/index.d.ts b/packages/optimizely-sdk/lib/index.d.ts index 867de1af5..f13563e68 100644 --- a/packages/optimizely-sdk/lib/index.d.ts +++ b/packages/optimizely-sdk/lib/index.d.ts @@ -130,7 +130,7 @@ declare module '@optimizely/optimizely-sdk' { featureKey: string, userId: string, attributes?: import('./shared_types').UserAttributes - ): { [variableKey: string]: unknown }; + ): { [variableKey: string]: unknown } | null; getOptimizelyConfig(): import('./shared_types').OptimizelyConfig | null; onReady(options?: { timeout?: number }): Promise<{ success: boolean; reason?: string }>; close(): Promise<{ success: boolean; reason?: string }>; diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 175e2a2fa..dc8a71997 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -17,7 +17,7 @@ import { sprintf, objectValues } from '@optimizely/js-sdk-utils'; import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; import * as eventProcessor from '@optimizely/js-sdk-event-processor'; import { EventTags, EventDispatcher } from '@optimizely/optimizely-sdk'; -import { FeatureFlag } from '../core/project_config/entities'; +import { FeatureFlag, FeatureVariable } from '../core/project_config/entities'; import { UserAttributes, Variation } from '../shared_types'; import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; import { ProjectConfigManager, createProjectConfigManager }from '@optimizely/optimizely-sdk/lib/core/projet_config_manager'; @@ -774,7 +774,7 @@ export default class Optimizely { variableKey: string, variableType: string | null, userId: string, - attributes: UserAttributes) { + attributes?: UserAttributes): unknown { if (!this.__validateInputs({ feature_key: featureKey, variable_key: variableKey, user_id: userId }, attributes)) { return null; } @@ -841,20 +841,22 @@ export default class Optimizely { * available variation or not. Also logs the appropriate message explaining how it * evaluated the value of the variable. * - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {boolean} featureEnabled Boolean indicating if feature is enabled or not - * @param {object} variation variation returned by decision service - * @param {object} variable varible whose value is being evaluated - * @param {string} userId ID for the user - * @return {string|null} String value of the variable or null if the config Obj - * is null + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {boolean} featureEnabled Boolean indicating if feature is enabled or not + * @param {Variation} variation variation returned by decision service + * @param {FeatureVariable} variable varible whose value is being evaluated + * @param {string} userId ID for the user + * @return {string|null} String value of the variable or null if the + * config Obj is null */ _getFeatureVariableValueFromVariation( featureKey: string, featureEnabled: boolean, variation: Variation, - variable: , userId) { + variable: FeatureVariable, + userId: string + ): string | null { var configObj = this.projectConfigManager.getConfig(); if (!configObj) { return null; @@ -918,24 +920,28 @@ export default class Optimizely { /** * Returns value for the given boolean variable attached to the given feature * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {boolean|null} Boolean value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {UserAttributes} attributes Optional user attributes + * @return {boolean|null} Boolean value of the variable, or null if the + * feature key is invalid, the variable key is invalid, + * or there is a mismatch with the type of the variable. */ - getFeatureVariableBoolean(featureKey, variableKey, userId, attributes) { + getFeatureVariableBoolean( + featureKey: string, + variableKey: string, + userId: string, + attributes?: UserAttributes + ): boolean | null { try { if (!this.__isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableBoolean')); return null; } - return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.BOOLEAN, userId, attributes); + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.BOOLEAN, userId, attributes) as boolean | null; } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); @@ -946,24 +952,29 @@ export default class Optimizely { /** * Returns value for the given double variable attached to the given feature * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {number|null} Number value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {UserAttributes} attributes Optional user attributes + * @return {number|null} Number value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable */ - getFeatureVariableDouble(featureKey, variableKey, userId, attributes) { + getFeatureVariableDouble( + featureKey:string, + variableKey: string, + userId: string, + attributes?: UserAttributes + ): number | null { try { if (!this.__isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableDouble')); return null; } - return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.DOUBLE, userId, attributes); + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.DOUBLE, userId, attributes) as number | null; } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); @@ -974,24 +985,29 @@ export default class Optimizely { /** * Returns value for the given integer variable attached to the given feature * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {number|null} Number value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {UserAttributes} attributes Optional user attributes + * @return {number|null} Number value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable */ - getFeatureVariableInteger(featureKey, variableKey, userId, attributes) { + getFeatureVariableInteger( + featureKey: string, + variableKey: string, + userId: string, + attributes?: UserAttributes + ): number | null { try { if (!this.__isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableInteger')); return null; } - return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.INTEGER, userId, attributes); + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.INTEGER, userId, attributes) as number | null; } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); @@ -1002,24 +1018,29 @@ export default class Optimizely { /** * Returns value for the given string variable attached to the given feature * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {string|null} String value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {UserAttributes} attributes Optional user attributes + * @return {string|null} String value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable */ - getFeatureVariableString(featureKey, variableKey, userId, attributes) { + getFeatureVariableString( + featureKey: string, + variableKey: string, + userId: string, + attributes?: UserAttributes + ): string | null { try { if (!this.__isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString')); return null; } - return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.STRING, userId, attributes); + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.STRING, userId, attributes) as string | null; } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); @@ -1030,18 +1051,23 @@ export default class Optimizely { /** * Returns value for the given json variable attached to the given feature * flag. - * @param {string} featureKey Key of the feature whose variable's value is - * being accessed - * @param {string} variableKey Key of the variable whose value is being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {object|null} Object value of the variable, or null if the - * feature key is invalid, the variable key is - * invalid, or there is a mismatch with the type - * of the variable + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {UserAttributes} attributes Optional user attributes + * @return {unknown} Object value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable */ - getFeatureVariableJSON(featureKey, variableKey, userId, attributes) { + getFeatureVariableJSON( + featureKey: string, + variableKey: string, + userId: string, + attributes: UserAttributes + ): unknown { try { if (!this.__isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableJSON')); @@ -1058,14 +1084,18 @@ export default class Optimizely { /** * Returns values for all the variables attached to the given feature * flag. - * @param {string} featureKey Key of the feature whose variables are being - * accessed - * @param {string} userId ID for the user - * @param {Object} attributes Optional user attributes - * @return {object|null} Object containing all the variables, or null if the - * feature key is invalid + * @param {string} featureKey Key of the feature whose variables are being + * accessed + * @param {string} userId ID for the user + * @param {UserAttributes} attributes Optional user attributes + * @return {object|null} Object containing all the variables, or null if the + * feature key is invalid */ - getAllFeatureVariables(featureKey, userId, attributes) { + getAllFeatureVariables( + featureKey: string, + userId: string, + attributes?: UserAttributes + ): { [variableKey: string]: unknown } | null { try { if (!this.__isValidInstance()) { this.logger.log(LOG_LEVEL.ERROR, sprintf(LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getAllFeatureVariables')); From dd5fb2ada7ff6c98694e463139e0a6b70934df07 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 25 Sep 2020 16:34:38 -0700 Subject: [PATCH 14/22] Convert onReady --- .../project_config_manager.d.ts | 2 +- .../optimizely-sdk/lib/optimizely/index.ts | 55 +++++++++++-------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts index 785a9bd63..8711e88b3 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts +++ b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts @@ -46,7 +46,7 @@ declare module '@optimizely/optimizely-sdk/lib/core/projet_config_manager' { * @param {Function} listener * @return {Function} */ - onUpdate(listener: (config: ProjectConfig) => void): () => void; + onUpdate(listener: (config: ProjectConfig) => void): (() => void) | null; /** * Returns a Promise that fulfills when this ProjectConfigManager is ready to diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index dc8a71997..1de9f8061 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -64,20 +64,20 @@ const DEFAULT_ONREADY_TIMEOUT = 30000; * @param {string} config.sdkKey */ export default class Optimizely { + private __isOptimizelyConfigValid: boolean; + private __disposeOnUpdate: (() => void ) | null; + private __readyPromise: Promise<{ success: boolean; reason?: string }>; //TODO + private __readyTimeouts: any;//TODO + private __nextReadyTimeoutId: any;//TODO private clientEngine: string; private clientVersion: string; private errorHandler: ErrorHandler; private eventDispatcher: EventDispatcher; - private __isOptimizelyConfigValid: boolean; private logger: LogHandler; private projectConfigManager: ProjectConfigManager; - private __disposeOnUpdate: () => void; private notificationCenter: NotificationCenter; private decisionService: DecisionService; private eventProcessor: eventProcessor.EventProcessor; - private __readyPromise: any; //TODO - private __readyTimeouts: any;//TODO - private __nextReadyTimeoutId: any;//TODO constructor(config: ProjectConfig) { @@ -624,7 +624,7 @@ export default class Optimizely { featureEnabled = variation.featureEnabled; if ( decision.decisionSource === DECISION_SOURCES.FEATURE_TEST && - decision.experiment !== null && + decision.experiment !== null && decision.variation !== null ) { sourceInfo = { @@ -808,7 +808,7 @@ export default class Optimizely { var sourceInfo = {}; if ( decision.decisionSource === DECISION_SOURCES.FEATURE_TEST && - decision.experiment !== null && + decision.experiment !== null && decision.variation !== null ) { sourceInfo = { @@ -853,7 +853,7 @@ export default class Optimizely { _getFeatureVariableValueFromVariation( featureKey: string, featureEnabled: boolean, - variation: Variation, + variation: Variation | null, variable: FeatureVariable, userId: string ): string | null { @@ -1120,12 +1120,15 @@ export default class Optimizely { var featureEnabled = decision.variation !== null ? decision.variation.featureEnabled : false; var allVariables = {}; - featureFlag.variables.forEach(function (variable) { + featureFlag.variables.forEach(function (this: Optimizely, variable: FeatureVariable) { allVariables[variable.key] = this._getFeatureVariableValueFromVariation(featureKey, featureEnabled, decision.variation, variable, userId); }.bind(this)); var sourceInfo = {}; - if (decision.decisionSource === DECISION_SOURCES.FEATURE_TEST) { + if (decision.decisionSource === DECISION_SOURCES.FEATURE_TEST && + decision.experiment !== null && + decision.variation !== null + ) { sourceInfo = { experimentKey: decision.experiment.key, variationKey: decision.variation.key, @@ -1233,7 +1236,7 @@ export default class Optimizely { * * @return {Promise} */ - close() { + close(): Promise<{ success: boolean; reason?: string }> { try { var eventProcessorStoppedPromise = this.eventProcessor.stop(); if (this.__disposeOnUpdate) { @@ -1244,7 +1247,7 @@ export default class Optimizely { this.projectConfigManager.stop(); } Object.keys(this.__readyTimeouts).forEach( - function(readyTimeoutId) { + function(this: Optimizely, readyTimeoutId: string ) { var readyTimeoutRecord = this.__readyTimeouts[readyTimeoutId]; clearTimeout(readyTimeoutRecord.readyTimeout); readyTimeoutRecord.onClose(); @@ -1301,31 +1304,35 @@ export default class Optimizely { * @param {number|undefined} options.timeout * @return {Promise} */ - onReady(options) { - var timeout; + onReady(options?: { timeout?: number }): Promise<{ success: boolean; reason?: string }> { + let timeoutValue: number | undefined; if (typeof options === 'object' && options !== null) { - timeout = options.timeout; + if (options.timeout !== undefined) { + timeoutValue = options.timeout; + } } - if (!isSafeInteger(timeout)) { - timeout = DEFAULT_ONREADY_TIMEOUT; + if (!isSafeInteger(timeoutValue)) { + timeoutValue = DEFAULT_ONREADY_TIMEOUT; } - var resolveTimeoutPromise; - var timeoutPromise = new Promise(function(resolve) { + type Resolve = (value?: unknown) => void; + + let resolveTimeoutPromise: any; + var timeoutPromise = new Promise(function(resolve: Resolve) { resolveTimeoutPromise = resolve; }); var timeoutId = this.__nextReadyTimeoutId; this.__nextReadyTimeoutId++; - var onReadyTimeout = function() { + var onReadyTimeout = function(this: Optimizely) { delete this.__readyTimeouts[timeoutId]; resolveTimeoutPromise({ success: false, - reason: sprintf('onReady timeout expired after %s ms', timeout), + reason: sprintf('onReady timeout expired after %s ms', timeoutValue), }); }.bind(this); - var readyTimeout = setTimeout(onReadyTimeout, timeout); + var readyTimeout = setTimeout(onReadyTimeout, timeoutValue); var onClose = function() { resolveTimeoutPromise({ success: false, @@ -1339,7 +1346,7 @@ export default class Optimizely { }; this.__readyPromise.then( - function() { + function(this: Optimizely) { clearTimeout(readyTimeout); delete this.__readyTimeouts[timeoutId]; resolveTimeoutPromise({ @@ -1348,6 +1355,6 @@ export default class Optimizely { }.bind(this) ); - return Promise.race([this.__readyPromise, timeoutPromise]); + return Promise.race([this.__readyPromise, timeoutPromise]) as Promise<{ success: boolean; reason?: string | undefined; }>; }; } From 615ab275f13a07436a59e9ab9cd070faeb6840f5 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 25 Sep 2020 20:08:54 -0700 Subject: [PATCH 15/22] Fix imports and got to a working state --- .../lib/core/decision_service/index.d.ts | 161 ++++++----- .../lib/core/event_builder/event_helpers.d.ts | 75 +++-- .../lib/core/event_builder/index.d.ts | 76 +++-- .../lib/core/notification_center/index.d.ts | 136 ++++----- .../lib/core/project_config/index.d.ts | 260 +++++++++--------- .../project_config_manager.d.ts | 122 ++++---- packages/optimizely-sdk/lib/index.d.ts | 48 +--- .../optimizely-sdk/lib/optimizely/index.ts | 26 +- packages/optimizely-sdk/lib/shared_types.ts | 30 +- 9 files changed, 455 insertions(+), 479 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.d.ts b/packages/optimizely-sdk/lib/core/decision_service/index.d.ts index 29f5aee1c..d903bd4d0 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.d.ts +++ b/packages/optimizely-sdk/lib/core/decision_service/index.d.ts @@ -13,95 +13,94 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { LogHandler } from '@optimizely/js-sdk-logging'; +import { ProjectConfig } from '../project_config'; +import { UserAttributes, UserProfileService, Experiment, Variation } from '../../shared_types'; +import { FeatureFlag } from '../project_config/entities'; -declare module '@optimizely/optimizely-sdk/lib/core/decision_service' { - import { LogHandler } from '@optimizely/js-sdk-logging'; - import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; +/** + * Creates an instance of the DecisionService. + * @param {Options} options Configuration options + * @return {DecisionService} An instance of the DecisionService + */ +export function createDecisionService(options: Options): DecisionService; + +export interface DecisionService { /** - * Creates an instance of the DecisionService. - * @param {Options} options Configuration options - * @return {DecisionService} An instance of the DecisionService + * Gets variation where visitor will be bucketed. + * @param {ProjectConfig} configObj The parsed project configuration object + * @param {string} experimentKey + * @param {string} userId + * @param {UserAttributes} attributes + * @return {string|null} The variation the user is bucketed into. */ - export function createDecisionService(options: Options): DecisionService; - - export interface DecisionService { + getVariation( + configObj: ProjectConfig, + experimentKey: string, + userId: string, + attributes?: UserAttributes + ): string | null; - /** - * Gets variation where visitor will be bucketed. - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {string} experimentKey - * @param {string} userId - * @param {UserAttributes} attributes - * @return {string|null} The variation the user is bucketed into. - */ - getVariation( - configObj: ProjectConfig, - experimentKey: string, - userId: string, - attributes?: import('../../shared_types').UserAttributes - ): string | null; - - /** - * Given a feature, user ID, and attributes, returns an object representing a - * decision. If the user was bucketed into a variation for the given feature - * and attributes, the returned decision object will have variation and - * experiment properties (both objects), as well as a decisionSource property. - * decisionSource indicates whether the decision was due to a rollout or an - * experiment. - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {FeatureFlag} feature A feature flag object from project configuration - * @param {string} userId A string identifying the user, for bucketing - * @param {unknown} attributes Optional user attributes - * @return {Decision} An object with experiment, variation, and decisionSource - * properties. If the user was not bucketed into a variation, the variation - * property is null. - */ - getVariationForFeature( - configObj: ProjectConfig, - feature: import('../project_config/entities').FeatureFlag, - userId: string, - attributes: unknown - ): Decision; + /** + * Given a feature, user ID, and attributes, returns an object representing a + * decision. If the user was bucketed into a variation for the given feature + * and attributes, the returned decision object will have variation and + * experiment properties (both objects), as well as a decisionSource property. + * decisionSource indicates whether the decision was due to a rollout or an + * experiment. + * @param {ProjectConfig} configObj The parsed project configuration object + * @param {FeatureFlag} feature A feature flag object from project configuration + * @param {string} userId A string identifying the user, for bucketing + * @param {unknown} attributes Optional user attributes + * @return {Decision} An object with experiment, variation, and decisionSource + * properties. If the user was not bucketed into a variation, the variation + * property is null. + */ + getVariationForFeature( + configObj: ProjectConfig, + feature: FeatureFlag, + userId: string, + attributes: unknown + ): Decision; - /** - * Removes forced variation for given userId and experimentKey - * @param {unknown} userId String representing the user id - * @param {string} experimentId Number representing the experiment id - * @param {string} experimentKey Key representing the experiment id - * @throws If the user id is not valid or not in the forced variation map - */ - removeForcedVariation(userId: unknown, experimentId: string, experimentKey: string): void; + /** + * Removes forced variation for given userId and experimentKey + * @param {unknown} userId String representing the user id + * @param {string} experimentId Number representing the experiment id + * @param {string} experimentKey Key representing the experiment id + * @throws If the user id is not valid or not in the forced variation map + */ + removeForcedVariation(userId: unknown, experimentId: string, experimentKey: string): void; - /** - * Gets the forced variation key for the given user and experiment. - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} experimentKey Key for experiment. - * @param {string} userId The user Id. - * @return {string|null} Variation key that specifies the variation which the given user and experiment should be forced into. - */ - getForcedVariation(configObj: ProjectConfig, experimentKey: string, userId: string): string | null; + /** + * Gets the forced variation key for the given user and experiment. + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Key for experiment. + * @param {string} userId The user Id. + * @return {string|null} Variation key that specifies the variation which the given user and experiment should be forced into. + */ + getForcedVariation(configObj: ProjectConfig, experimentKey: string, userId: string): string | null; - /** - * Sets the forced variation for a user in a given experiment - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} experimentKey Key for experiment. - * @param {string} userId The user Id. - * @param {unknown} variationKey Key for variation. If null, then clear the existing experiment-to-variation mapping - * @return {boolean} A boolean value that indicates if the set completed successfully. - */ - setForcedVariation(configObj: ProjectConfig, experimentKey: string, userId: string, variationKey: unknown): boolean; - } + /** + * Sets the forced variation for a user in a given experiment + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Key for experiment. + * @param {string} userId The user Id. + * @param {unknown} variationKey Key for variation. If null, then clear the existing experiment-to-variation mapping + * @return {boolean} A boolean value that indicates if the set completed successfully. + */ + setForcedVariation(configObj: ProjectConfig, experimentKey: string, userId: string, variationKey: unknown): boolean; +} - interface Options { - userProfileService: import('../../shared_types').UserProfileService | null; - logger: LogHandler; - UNSTABLE_conditionEvaluators: unknown; - } +interface Options { + userProfileService: UserProfileService | null; + logger: LogHandler; + UNSTABLE_conditionEvaluators: unknown; +} - interface Decision { - experiment: import('../../shared_types').Experiment | null; - variation: import('../../shared_types').Variation | null; - decisionSource: string; - } +interface Decision { + experiment: Experiment | null; + variation: Variation | null; + decisionSource: string; } diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts index 25731a687..c57c8c7ae 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/event_helpers.d.ts @@ -14,46 +14,45 @@ * limitations under the License. */ -declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { - import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; - import { EventTags } from '@optimizely/optimizely-sdk'; +import { ProjectConfig } from '../project_config'; +import { EventTags, UserAttributes } from '../../shared_types'; - interface ImpressionConfig { - experimentKey: string; - variationKey: string; - userId: string; - userAttributes?: import('../../shared_types').UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; - } +interface ImpressionConfig { + experimentKey: string; + variationKey: string; + userId: string; + userAttributes?: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; +} - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface ImpressionEvent {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ImpressionEvent {} - interface ConversionConfig { - eventKey: string; - eventTags?: EventTags; - userId: string; - userAttributes?: import('../../shared_types').UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; - } +interface ConversionConfig { + eventKey: string; + eventTags?: EventTags; + userId: string; + userAttributes?: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; +} - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface ConversionEvent {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ConversionEvent {} - /** - * Creates an ImpressionEvent object from decision data - * @param {ImpressionConfig} config - * @return {ImpressionEvent} an ImpressionEvent object - */ - export function buildImpressionEvent(config: ImpressionConfig): ImpressionEvent; - /** - * Creates a ConversionEvent object from track - * @param {ConversionConfig} config - * @return {ConversionEvent} a ConversionEvent object - */ - export function buildConversionEvent(config: ConversionConfig): ConversionEvent; -} +/** + * Creates an ImpressionEvent object from decision data + * @param {ImpressionConfig} config + * @return {ImpressionEvent} an ImpressionEvent object + */ +export function buildImpressionEvent(config: ImpressionConfig): ImpressionEvent; + +/** + * Creates a ConversionEvent object from track + * @param {ConversionConfig} config + * @return {ConversionEvent} a ConversionEvent object + */ +export function buildConversionEvent(config: ConversionConfig): ConversionEvent; diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts index ddd21b667..47718911d 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts @@ -13,46 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ProjectConfig } from '../project_config'; +import { LogHandler } from '@optimizely/js-sdk-logging'; +import { EventTags, UserAttributes } from '../../shared_types'; +import { Event as EventLoggingEndpoint } from '@optimizely/optimizely-sdk'; -declare module '@optimizely/optimizely-sdk/lib/core/event_builder' { - import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; - import { LogHandler } from '@optimizely/js-sdk-logging'; - import { EventTags } from '@optimizely/optimizely-sdk'; - import { Event as EventLoggingEndpoint } from '@optimizely/optimizely-sdk'; +interface ImpressionOptions { + attributes?: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + experimentId: string; + eventKey?: string; + variationId: string; + logger?: LogHandler; + userId: string; +} - interface ImpressionOptions { - attributes?: import('../../shared_types').UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; - experimentId: string; - eventKey?: string; - variationId: string; - logger: LogHandler; - userId: string; - } +interface ConversionEventOptions { + attributes?: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; + eventKey: string; + logger: LogHandler; + userId: string; + eventTags?: EventTags; +} - interface ConversionEventOptions { - attributes: import('../../shared_types').UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; - eventKey: string; - logger: LogHandler; - userId: string; - eventTags: EventTags; - } +/** + * Create impression event params to be sent to the logging endpoint + * @param {ImpressionOptions} options Object containing values needed to build impression event + * @return {EventLoggingEndpoint} Params to be used in impression event logging endpoint call + */ +export function getImpressionEvent(options: ImpressionOptions): EventLoggingEndpoint; - /** - * Create impression event params to be sent to the logging endpoint - * @param {ImpressionOptions} options Object containing values needed to build impression event - * @return {EventLoggingEndpoint} Params to be used in impression event logging endpoint call - */ - export function getImpressionEvent(options: ImpressionOptions): EventLoggingEndpoint; - /** - * Create conversion event params to be sent to the logging endpoint - * @param {ConversionEventOptions} options Object containing values needed to build conversion event - * @return {EventLoggingEndpoint} Params to be used in conversion event logging endpoint call - */ - export function getConversionEvent(options: ConversionEventOptions): EventLoggingEndpoint; -} +/** + * Create conversion event params to be sent to the logging endpoint + * @param {ConversionEventOptions} options Object containing values needed to build conversion event + * @return {EventLoggingEndpoint} Params to be used in conversion event logging endpoint call + */ +export function getConversionEvent(options: ConversionEventOptions): EventLoggingEndpoint; diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.d.ts b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts index 85f001320..4f61f9233 100644 --- a/packages/optimizely-sdk/lib/core/notification_center/index.d.ts +++ b/packages/optimizely-sdk/lib/core/notification_center/index.d.ts @@ -14,80 +14,84 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; +import { + Event, + EventTags, + UserAttributes, + Experiment, + Variation +} from '../../shared_types'; -declare module '@optimizely/optimizely-sdk/lib/core/notification_center' { - import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; - import { Event, EventTags } from '@optimizely/optimizely-sdk'; - export enum NOTIFICATION_TYPES { - ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', - DECISION = 'DECISION:type, userId, attributes, decisionInfo', - LOG_EVENT = 'LOG_EVENT:logEvent', - OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE', - TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event', - } - - export enum DECISION_NOTIFICATION_TYPES { - AB_TEST = 'ab-test', - FEATURE = 'feature', - FEATURE_TEST = 'feature-test', - FEATURE_VARIABLE = 'feature-variable', - ALL_FEATURE_VARIABLES = 'all-feature-variables', - } +export enum NOTIFICATION_TYPES { + ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', + DECISION = 'DECISION:type, userId, attributes, decisionInfo', + LOG_EVENT = 'LOG_EVENT:logEvent', + OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE', + TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event', +} - export type Options = { - logger: LogHandler; - errorHandler: ErrorHandler; - }; +export enum DECISION_NOTIFICATION_TYPES { + AB_TEST = 'ab-test', + FEATURE = 'feature', + FEATURE_TEST = 'feature-test', + FEATURE_VARIABLE = 'feature-variable', + ALL_FEATURE_VARIABLES = 'all-feature-variables', +} - export type SourceInfo = { - experimentKey?: string; - variationKey?: string; - }; +export type Options = { + logger: LogHandler; + errorHandler: ErrorHandler; +}; - export type VariableValues = { - [name: string]: unknown; - }; +export type SourceInfo = { + experimentKey?: string; + variationKey?: string; +}; - export type DecisionInfo = { - experimentKey?: string; - variationKey?: string | null; - featureKey?: string; - featureEnabled?: boolean; - source?: string; - sourceInfo?: SourceInfo; - variableKey?: string; - variableValue?: unknown; - variableValues?: VariableValues; - variableType?: string; - }; +export type VariableValues = { + [name: string]: unknown; +}; - export interface NotificationData { - // type?: DECISION_NOTIFICATION_TYPES; - type?: string; - userId?: string; - attributes?: import('../../shared_types').UserAttributes; - decisionInfo?: DecisionInfo; - experiment?: import('../../shared_types').Experiment; - variation?: import('../../shared_types').Variation; - logEvent?: Event; - eventKey?: string; - eventTags?: EventTags; - } +export type DecisionInfo = { + experimentKey?: string; + variationKey?: string | null; + featureKey?: string; + featureEnabled?: boolean; + source?: string; + sourceInfo?: SourceInfo; + variableKey?: string; + variableValue?: unknown; + variableValues?: VariableValues; + variableType?: string; +}; - export interface NotificationCenter { - /** - * Fires notifications for the argument type. All registered callbacks for this type will be - * called. The notificationData object will be passed on to callbacks called. - * @param {NOTIFICATION_TYPES} notificationType One of NOTIFICATION_TYPES - * @param {NotificationData} notificationData Will be passed to callbacks called - */ - sendNotifications(notificationType: NOTIFICATION_TYPES, notificationData?: NotificationData): void; - } +export interface NotificationData { + // type?: DECISION_NOTIFICATION_TYPES; + type?: string; + userId?: string; + attributes?: UserAttributes; + decisionInfo?: DecisionInfo; + experiment?: Experiment; + variation?: Variation; + logEvent?: Event; + eventKey?: string; + eventTags?: EventTags; +} +export interface NotificationCenter { /** - * Create an instance of NotificationCenter - * @param {Options} options - * @returns {NotificationCenter} An instance of NotificationCenter + * Fires notifications for the argument type. All registered callbacks for this type will be + * called. The notificationData object will be passed on to callbacks called. + * @param {NOTIFICATION_TYPES} notificationType One of NOTIFICATION_TYPES + * @param {NotificationData} notificationData Will be passed to callbacks called */ - export function createNotificationCenter(options: Options): NotificationCenter; + sendNotifications(notificationType: NOTIFICATION_TYPES, notificationData?: NotificationData): void; } + +/** + * Create an instance of NotificationCenter + * @param {Options} options + * @returns {NotificationCenter} An instance of NotificationCenter + */ +export function createNotificationCenter(options: Options): NotificationCenter; diff --git a/packages/optimizely-sdk/lib/core/project_config/index.d.ts b/packages/optimizely-sdk/lib/core/project_config/index.d.ts index ef6c50a66..65b6f76c0 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.d.ts +++ b/packages/optimizely-sdk/lib/core/project_config/index.d.ts @@ -13,143 +13,143 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; +import { EventDispatcher } from '@optimizely/js-sdk-event-processor'; +import { DatafileOptions, UserProfileService, Experiment, Variation } from '../../shared_types'; +import { FeatureFlag, FeatureVariable } from './entities'; -declare module '@optimizely/optimizely-sdk/lib/core/project_config' { - import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; - import { EventDispatcher, DatafileOptions } from '@optimizely/optimizely-sdk'; - // eslint-disable-next-line @typescript-eslint/no-empty-interface - export interface ProjectConfig { - revision: string; - projectId: string; - featureKeyMap?: { - [key: string]: import('./entities').FeatureFlag - }; - clientEngine: string; - clientVersion?: string; - errorHandler: ErrorHandler; - eventDispatcher: EventDispatcher; - isValidInstance: boolean; - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile: object | string; - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - jsonSchemaValidator?: object; - sdkKey?: string; - userProfileService?: import('../../shared_types').UserProfileService | null; - UNSTABLE_conditionEvaluators?: unknown; - eventFlushInterval?: number; - eventBatchSize?: number; - datafileOptions?: DatafileOptions; - eventMaxQueueSize?: number; - logger: LogHandler; - experimentKeyMap:{[key: string]: import('../../shared_types').Experiment}; - } - /** - * Determine for given experiment if event is running, which determines whether should be dispatched or not - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} experimentKey Experiment key for which the status is to be determined - * @return {boolean} True is the experiment is running - * False is the experiment is not running - * - */ - export function isRunning(configObj: ProjectConfig, experimentKey: string): boolean; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ProjectConfig { + revision: string; + projectId: string; + featureKeyMap?: { + [key: string]: FeatureFlag + }; + clientEngine: string; + clientVersion?: string; + errorHandler: ErrorHandler; + eventDispatcher: EventDispatcher; + isValidInstance: boolean; + // TODO[OASIS-6649]: Don't use object type + // eslint-disable-next-line @typescript-eslint/ban-types + datafile: object | string; + // TODO[OASIS-6649]: Don't use object type + // eslint-disable-next-line @typescript-eslint/ban-types + jsonSchemaValidator?: object; + sdkKey?: string; + userProfileService?: UserProfileService | null; + UNSTABLE_conditionEvaluators?: unknown; + eventFlushInterval?: number; + eventBatchSize?: number; + datafileOptions?: DatafileOptions; + eventMaxQueueSize?: number; + logger: LogHandler; + experimentKeyMap:{[key: string]: Experiment}; +} +/** + * Determine for given experiment if event is running, which determines whether should be dispatched or not + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Experiment key for which the status is to be determined + * @return {boolean} True is the experiment is running + * False is the experiment is not running + * + */ +export function isRunning(configObj: ProjectConfig, experimentKey: string): boolean; - /** - * Get the variation ID given the experiment key and variation key - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} experimentKey Key of the experiment the variation belongs to - * @param {string} variationKey The variation key - * @return {string} the variation ID - */ - export function getVariationIdFromExperimentAndVariationKey(configObj: ProjectConfig, experimentKey: string, variationKey: string): string; +/** + * Get the variation ID given the experiment key and variation key + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Key of the experiment the variation belongs to + * @param {string} variationKey The variation key + * @return {string} the variation ID + */ +export function getVariationIdFromExperimentAndVariationKey(configObj: ProjectConfig, experimentKey: string, variationKey: string): string; - /** - * Get experiment ID for the provided experiment key - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} experimentKey Experiment key for which ID is to be determined - * @return {string} Experiment ID corresponding to the provided experiment key - * @throws If experiment key is not in datafile - */ - export function getExperimentId(configObj: ProjectConfig, experimentKey: string): string | never; +/** + * Get experiment ID for the provided experiment key + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Experiment key for which ID is to be determined + * @return {string} Experiment ID corresponding to the provided experiment key + * @throws If experiment key is not in datafile + */ +export function getExperimentId(configObj: ProjectConfig, experimentKey: string): string | never; - /** - * Check if the event with a specific key is present in the datafile - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} eventKey Event key for which event is to be determined - * @returns {boolean} True if key exists in the datafile - * False if key does not exist in the datafile - */ - export function eventWithKeyExists(configObj: ProjectConfig, eventKey: string): boolean; +/** + * Check if the event with a specific key is present in the datafile + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} eventKey Event key for which event is to be determined + * @returns {boolean} True if key exists in the datafile + * False if key does not exist in the datafile + */ +export function eventWithKeyExists(configObj: ProjectConfig, eventKey: string): boolean; - /** - * Check if the experiment is belongs to any feature - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} experimentId Experiment ID of an experiment - * @returns {boolean} True if experiement belongs to any feature - * False if experiement does not belong to any feature - */ - export function isFeatureExperiment(configObj: ProjectConfig, experimentId: string): boolean; +/** + * Check if the experiment is belongs to any feature + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentId Experiment ID of an experiment + * @returns {boolean} True if experiement belongs to any feature + * False if experiement does not belong to any feature + */ +export function isFeatureExperiment(configObj: ProjectConfig, experimentId: string): boolean; - /** - * Get feature from provided feature key. Log an error if no feature exists in - * the project config with the given key. - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} featureKey Key of a feature for which feature is to be determined - * @param {LogHandler} logger Logger instance - * @return {FeatureFlag|null} Feature object, or null if no feature with the given - * key exists - */ - export function getFeatureFromKey(configObj: ProjectConfig, featureKey: string, logger: LogHandler): import('./entities').FeatureFlag | null; +/** + * Get feature from provided feature key. Log an error if no feature exists in + * the project config with the given key. + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} featureKey Key of a feature for which feature is to be determined + * @param {LogHandler} logger Logger instance + * @return {FeatureFlag|null} Feature object, or null if no feature with the given + * key exists + */ +export function getFeatureFromKey(configObj: ProjectConfig, featureKey: string, logger: LogHandler): FeatureFlag | null; - /** - * Get the variable with the given key associated with the feature with the - * given key. If the feature key or the variable key are invalid, log an error - * message. - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} featureKey Key of a feature for which feature is to be determined - * @param {string} variableKey Key of a variable for which variable is to be determined - * @param {LogHandler} logger Logger instances - * @return {FeatureVariable|null} Variable object, or null one or both of the given - * feature and variable keys are invalid - */ - export function getVariableForFeature(configObj: ProjectConfig, featureKey: string, variableKey: string, logger: LogHandler): import('./entities').FeatureVariable | null; +/** + * Get the variable with the given key associated with the feature with the + * given key. If the feature key or the variable key are invalid, log an error + * message. + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} featureKey Key of a feature for which feature is to be determined + * @param {string} variableKey Key of a variable for which variable is to be determined + * @param {LogHandler} logger Logger instances + * @return {FeatureVariable|null} Variable object, or null one or both of the given + * feature and variable keys are invalid + */ +export function getVariableForFeature(configObj: ProjectConfig, featureKey: string, variableKey: string, logger: LogHandler): FeatureVariable | null; - /** - * Given a variable value in string form, try to cast it to the argument type. - * If the type cast succeeds, return the type casted value, otherwise log an - * error and return null. - * @param {string} variableValue Variable value in string form - * @param {string} type Type of the variable whose value was passed - * in the first argument. Must be one of - * FEATURE_VARIABLE_TYPES in - * lib/utils/enums/index.js. The return value's - * type is determined by this argument (boolean - * for BOOLEAN, number for INTEGER or DOUBLE, - * and string for STRING). - * @param {LogHandler} logger Logger instance - * @returns {T} Variable value of the appropriate type, or - * null if the type cast failed - */ - export function getTypeCastValue(variableValue: string, type: string, logger: LogHandler): T; +/** + * Given a variable value in string form, try to cast it to the argument type. + * If the type cast succeeds, return the type casted value, otherwise log an + * error and return null. + * @param {string} variableValue Variable value in string form + * @param {string} type Type of the variable whose value was passed + * in the first argument. Must be one of + * FEATURE_VARIABLE_TYPES in + * lib/utils/enums/index.js. The return value's + * type is determined by this argument (boolean + * for BOOLEAN, number for INTEGER or DOUBLE, + * and string for STRING). + * @param {LogHandler} logger Logger instance + * @returns {T} Variable value of the appropriate type, or + * null if the type cast failed + */ +export function getTypeCastValue(variableValue: string, type: string, logger: LogHandler): T; - /** - * Get the value of the given variable for the given variation. If the given - * variable has no value for the given variation, return null. Log an error message if the variation is invalid. If the - * variable or variation are invalid, return null. - * @param {ProjectConfig} projectConfig - * @param {FeatureVariable} variable - * @param {Variation} variation - * @param {LogHandler} logger - * @return {string|null} The value of the given variable for the given - * variation, or null if the given variable has no value - * for the given variation or if the variation or variable are invalid - */ - export function getVariableValueForVariation( - projectConfig: ProjectConfig, - variable: import('./entities').FeatureVariable, - variation: import('../../shared_types').Variation, - logger: LogHandler - ): string | null; -} +/** + * Get the value of the given variable for the given variation. If the given + * variable has no value for the given variation, return null. Log an error message if the variation is invalid. If the + * variable or variation are invalid, return null. + * @param {ProjectConfig} projectConfig + * @param {FeatureVariable} variable + * @param {Variation} variation + * @param {LogHandler} logger + * @return {string|null} The value of the given variable for the given + * variation, or null if the given variable has no value + * for the given variation or if the variation or variable are invalid + */ +export function getVariableValueForVariation( + projectConfig: ProjectConfig, + variable: FeatureVariable, + variation: Variation, + logger: LogHandler +): string | null; diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts index 8711e88b3..f5601f8f0 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts +++ b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.d.ts @@ -13,73 +13,71 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ProjectConfig } from '../project_config'; +import { OptimizelyConfig } from '../../shared_types' -declare module '@optimizely/optimizely-sdk/lib/core/projet_config_manager' { - import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; - - /** - * ProjectConfigManager provides project config objects via its methods - * getConfig and onUpdate. It uses a DatafileManager to fetch datafiles. It is - * responsible for parsing and validating datafiles, and converting datafile - * JSON objects into project config objects. - * @param {ProjectConfig} config - * @param {Object|string} config.datafile - * @param {Object} config.datafileOptions - * @param {Object} config.jsonSchemaValidator - * @param {string} config.sdkKey - */ - export function createProjectConfigManager(config: Partial): ProjectConfigManager; +/** +* ProjectConfigManager provides project config objects via its methods +* getConfig and onUpdate. It uses a DatafileManager to fetch datafiles. It is +* responsible for parsing and validating datafiles, and converting datafile +* JSON objects into project config objects. +* @param {ProjectConfig} config +* @param {Object|string} config.datafile +* @param {Object} config.datafileOptions +* @param {Object} config.jsonSchemaValidator +* @param {string} config.sdkKey +*/ +export function createProjectConfigManager(config: Partial): ProjectConfigManager; - export interface ProjectConfigManager { +export interface ProjectConfigManager { - /** - * Returns the current project config object, or null if no project config object - * is available - * @return {ProjectConfig|null} - */ - getConfig(): ProjectConfig | null; + /** + * Returns the current project config object, or null if no project config object + * is available + * @return {ProjectConfig|null} + */ + getConfig(): ProjectConfig | null; - /** - * Add a listener for project config updates. The listener will be called - * whenever this instance has a new project config object available. - * Returns a dispose function that removes the subscription - * @param {Function} listener - * @return {Function} - */ - onUpdate(listener: (config: ProjectConfig) => void): (() => void) | null; + /** + * Add a listener for project config updates. The listener will be called + * whenever this instance has a new project config object available. + * Returns a dispose function that removes the subscription + * @param {Function} listener + * @return {Function} + */ + onUpdate(listener: (config: ProjectConfig) => void): (() => void) | null; - /** - * Returns a Promise that fulfills when this ProjectConfigManager is ready to - * use (meaning it has a valid project config object), or has failed to become - * ready. - * - * Failure can be caused by the following: - * - At least one of sdkKey or datafile is not provided in the constructor argument - * - The provided datafile was invalid - * - The datafile provided by the datafile manager was invalid - * - The datafile manager failed to fetch a datafile - * - * The returned Promise is fulfilled with a result object containing these - * properties: - * - success (boolean): True if this instance is ready to use with a valid - * project config object, or false if it failed to - * become ready - * - reason (string=): If success is false, this is a string property with - * an explanatory message. - * @return {Promise} - */ - onReady(): Promise<{ success: boolean; reason?: string }>; + /** + * Returns a Promise that fulfills when this ProjectConfigManager is ready to + * use (meaning it has a valid project config object), or has failed to become + * ready. + * + * Failure can be caused by the following: + * - At least one of sdkKey or datafile is not provided in the constructor argument + * - The provided datafile was invalid + * - The datafile provided by the datafile manager was invalid + * - The datafile manager failed to fetch a datafile + * + * The returned Promise is fulfilled with a result object containing these + * properties: + * - success (boolean): True if this instance is ready to use with a valid + * project config object, or false if it failed to + * become ready + * - reason (string=): If success is false, this is a string property with + * an explanatory message. + * @return {Promise} + */ + onReady(): Promise<{ success: boolean; reason?: string }>; - /** - * Returns the optimizely config object - * @return {OptimizelyConfig} - */ - getOptimizelyConfig(): import('../../shared_types').OptimizelyConfig; + /** + * Returns the optimizely config object + * @return {OptimizelyConfig} + */ + getOptimizelyConfig(): OptimizelyConfig; - /** - * Stop the internal datafile manager and remove all update listeners - * @return {void} - */ - stop(): void; - } + /** + * Stop the internal datafile manager and remove all update listeners + * @return {void} + */ + stop(): void; } diff --git a/packages/optimizely-sdk/lib/index.d.ts b/packages/optimizely-sdk/lib/index.d.ts index f13563e68..b0fecf25c 100644 --- a/packages/optimizely-sdk/lib/index.d.ts +++ b/packages/optimizely-sdk/lib/index.d.ts @@ -18,6 +18,7 @@ declare module '@optimizely/optimizely-sdk' { import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; import * as enums from '@optimizely/optimizely-sdk/lib/utils/enums'; import * as logging from '@optimizely/optimizely-sdk/lib/plugins/logger'; + import { EventDispatcher } from '@optimizely/js-sdk-event-processor'; export { enums, logging }; export function setLogger(logger: LogHandler | null): void; @@ -30,19 +31,12 @@ declare module '@optimizely/optimizely-sdk' { export const eventDispatcher: EventDispatcher; - export interface DatafileOptions { - autoUpdate?: boolean; - updateInterval?: number; - urlTemplate?: string; - datafileAccessToken?: string; - } - // The options object given to Optimizely.createInstance. export interface Config { // TODO[OASIS-6649]: Don't use object type // eslint-disable-next-line @typescript-eslint/ban-types datafile?: object | string; - datafileOptions?: DatafileOptions; + datafileOptions?: import('./shared_types').DatafileOptions; errorHandler?: ErrorHandler; eventDispatcher?: EventDispatcher; logger?: LogHandler; @@ -72,7 +66,7 @@ declare module '@optimizely/optimizely-sdk' { eventKey: string, userId: string, attributes?: import('./shared_types').UserAttributes, - eventTags?: EventTags + eventTags?: import('./shared_types').EventTags ): void; getVariation( experimentKey: string, @@ -136,36 +130,6 @@ declare module '@optimizely/optimizely-sdk' { close(): Promise<{ success: boolean; reason?: string }>; } - // An event to be submitted to Optimizely, enabling tracking the reach and impact of - // tests and feature rollouts. - export interface Event { - // URL to which to send the HTTP request. - url: string; - // HTTP method with which to send the event. - httpVerb: 'POST'; - // Value to send in the request body, JSON-serialized. - // TODO[OASIS-6649]: Don't use any type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: any; - } - - export type EventDispatcherResponse = { - statusCode: number - } - - export type EventDispatcherCallback = (response: EventDispatcherResponse) => void - - export interface EventDispatcher { - /** - * @param event - * Event being submitted for eventual dispatch. - * @param callback - * After the event has at least been queued for dispatch, call this function to return - * control back to the Client. - */ - dispatchEvent: (event: Event, callback: EventDispatcherCallback) => void; - } - // NotificationCenter-related types export interface NotificationCenter { addNotificationListener( @@ -190,13 +154,9 @@ declare module '@optimizely/optimizely-sdk' { logEvent: Event; } - export type EventTags = { - [key: string]: string | number | boolean; - }; - export interface TrackListenerPayload extends ListenerPayload { eventKey: string; - eventTags: EventTags; + eventTags: import('./shared_types').EventTags; logEvent: Event; } } diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 1de9f8061..bdcf3327f 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -16,27 +16,21 @@ import { sprintf, objectValues } from '@optimizely/js-sdk-utils'; import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; import * as eventProcessor from '@optimizely/js-sdk-event-processor'; -import { EventTags, EventDispatcher } from '@optimizely/optimizely-sdk'; import { FeatureFlag, FeatureVariable } from '../core/project_config/entities'; -import { UserAttributes, Variation } from '../shared_types'; -import { ProjectConfig } from '@optimizely/optimizely-sdk/lib/core/project_config'; -import { ProjectConfigManager, createProjectConfigManager }from '@optimizely/optimizely-sdk/lib/core/projet_config_manager'; - +import { EventDispatcher } from '@optimizely/js-sdk-event-processor'; +import { UserAttributes, Variation, EventTags } from '../shared_types'; +import { createProjectConfigManager, ProjectConfigManager } from '../core/project_config/project_config_manager'; +import { createNotificationCenter, NotificationCenter } from '../core/notification_center'; +import { createDecisionService, DecisionService } from '../core/decision_service'; +import { getImpressionEvent, getConversionEvent } from '../core/event_builder'; +import { buildImpressionEvent, buildConversionEvent } from '../core/event_builder/event_helpers'; import { isSafeInteger } from '../utils/fns' import { validate } from '../utils/attributes_validator'; -// import * as decisionService from '../core/decision_service'; -import { DecisionService, createDecisionService} from '@optimizely/optimizely-sdk/lib/core/decision_service'; import * as enums from '../utils/enums'; -import { getImpressionEvent, getConversionEvent } from '@optimizely/optimizely-sdk/lib/core/event_builder'; -import { buildImpressionEvent, buildConversionEvent } from '@optimizely/optimizely-sdk/lib/core/event_builder'; import * as eventTagsValidator from '../utils/event_tags_validator'; -// import notificationCenter from '../core/notification_center'; -import { NotificationCenter, createNotificationCenter} from '@optimizely/optimizely-sdk/lib/core/notification_center'; -// import projectConfig from '../core/project_config'; -import * as projectConfig from '@optimizely/optimizely-sdk/lib/core/project_config'; +import * as projectConfig from '../core/project_config'; import * as userProfileServiceValidator from '../utils/user_profile_service_validator'; import * as stringValidator from '../utils/string_value_validator'; -// import * as projectConfigManager from '@optimizely/optimizely-sdk/lib/core/projet_config_manager'; const ERROR_MESSAGES = enums.ERROR_MESSAGES; const LOG_LEVEL = enums.LOG_LEVEL; @@ -80,7 +74,7 @@ export default class Optimizely { private eventProcessor: eventProcessor.EventProcessor; - constructor(config: ProjectConfig) { + constructor(config: projectConfig.ProjectConfig) { let clientEngine = config.clientEngine; if (enums.VALID_CLIENT_ENGINES.indexOf(clientEngine) === -1) { config.logger.log( @@ -105,7 +99,7 @@ export default class Optimizely { }); this.__disposeOnUpdate = this.projectConfigManager.onUpdate( - function(this: Optimizely, configObj: ProjectConfig) { + function(this: Optimizely, configObj: projectConfig.ProjectConfig) { this.logger.log( LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, MODULE_NAME, configObj.revision, configObj.projectId) diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index 0ff474c87..7c5e757fa 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -43,11 +43,38 @@ export interface UserProfile { }; } +export type EventTags = { + [key: string]: string | number | boolean; +}; + +// An event to be submitted to Optimizely, enabling tracking the reach and impact of +// tests and feature rollouts. +export interface Event { + // URL to which to send the HTTP request. + url: string; + // HTTP method with which to send the event. + httpVerb: 'POST'; + // Value to send in the request body, JSON-serialized. + // TODO[OASIS-6649]: Don't use any type + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params: any; +} + export interface UserProfileService { lookup(userId: string): UserProfile; save(profile: UserProfile): void; } +export interface DatafileOptions { + autoUpdate?: boolean; + updateInterval?: number; + urlTemplate?: string; + datafileAccessToken?: string; +} + +/** + * Optimizely Config Entities + */ export interface OptimizelyExperiment { id: string; key: string; @@ -56,9 +83,6 @@ export interface OptimizelyExperiment { }; } -/** - * Optimizely Config Entities - */ export interface OptimizelyVariable { id: string; key: string; From f7803d62c25bcbb327fe17d3b618931460d80ef1 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 25 Sep 2020 21:10:54 -0700 Subject: [PATCH 16/22] Fix lint --- .../lib/core/event_builder/index.d.ts | 2 +- .../optimizely-sdk/lib/optimizely/index.ts | 144 +++++++++--------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts index 47718911d..757bcf7a6 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/index.d.ts +++ b/packages/optimizely-sdk/lib/core/event_builder/index.d.ts @@ -16,7 +16,7 @@ import { ProjectConfig } from '../project_config'; import { LogHandler } from '@optimizely/js-sdk-logging'; import { EventTags, UserAttributes } from '../../shared_types'; -import { Event as EventLoggingEndpoint } from '@optimizely/optimizely-sdk'; +import { Event as EventLoggingEndpoint } from '../../shared_types'; interface ImpressionOptions { attributes?: UserAttributes; diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index bdcf3327f..d6ee0d1b3 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -18,7 +18,7 @@ import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; import * as eventProcessor from '@optimizely/js-sdk-event-processor'; import { FeatureFlag, FeatureVariable } from '../core/project_config/entities'; import { EventDispatcher } from '@optimizely/js-sdk-event-processor'; -import { UserAttributes, Variation, EventTags } from '../shared_types'; +import { UserAttributes, Variation, EventTags, OptimizelyConfig } from '../shared_types'; import { createProjectConfigManager, ProjectConfigManager } from '../core/project_config/project_config_manager'; import { createNotificationCenter, NotificationCenter } from '../core/notification_center'; import { createDecisionService, DecisionService } from '../core/decision_service'; @@ -161,7 +161,7 @@ export default class Optimizely { */ __isValidInstance(): boolean { return this.__isOptimizelyConfigValid && !!this.projectConfigManager.getConfig(); - }; + } /** * Buckets visitor and sends impression event to Optimizely. @@ -181,20 +181,20 @@ export default class Optimizely { return this.__notActivatingExperiment(experimentKey, userId); } - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return null; } try { - var variationKey = this.getVariation(experimentKey, userId, attributes); + const variationKey = this.getVariation(experimentKey, userId, attributes); if (variationKey === null) { return this.__notActivatingExperiment(experimentKey, userId); } // If experiment is not set to 'Running' status, log accordingly and return variation key if (!projectConfig.isRunning(configObj, experimentKey)) { - var shouldNotDispatchActivateLogMessage = sprintf( + const shouldNotDispatchActivateLogMessage = sprintf( LOG_MESSAGES.SHOULD_NOT_DISPATCH_ACTIVATE, MODULE_NAME, experimentKey @@ -208,7 +208,7 @@ export default class Optimizely { return variationKey; } catch (ex) { this.logger.log(LOG_LEVEL.ERROR, ex.message); - var failedActivationLogMessage = sprintf( + const failedActivationLogMessage = sprintf( LOG_MESSAGES.NOT_ACTIVATING_USER, MODULE_NAME, userId, @@ -223,7 +223,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Create an impression event and call the event dispatcher's dispatch method to @@ -252,7 +252,7 @@ export default class Optimizely { // TODO is it okay to not pass a projectConfig as second argument this.eventProcessor.process(impressionEvent); this.__emitNotificationCenterActivate(experimentKey, variationKey, userId, attributes); - }; + } /** * Emit the ACTIVATE notification on the notificationCenter @@ -261,7 +261,7 @@ export default class Optimizely { * @param {string} userId ID of user to whom the variation was shown * @param {UserAttributes} attributes Optional user attributes */ - __emitNotificationCenterActivate(experimentKey: string, variationKey: string, userId: string, attributes?: UserAttributes) { + __emitNotificationCenterActivate(experimentKey: string, variationKey: string, userId: string, attributes?: UserAttributes): void { const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return; @@ -292,7 +292,7 @@ export default class Optimizely { variation: variation, logEvent: impressionEvent, }); - }; + } /** * Sends conversion event to Optimizely. @@ -312,7 +312,7 @@ export default class Optimizely { return; } - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return; } @@ -328,7 +328,7 @@ export default class Optimizely { // remove null values from eventTags eventTags = this.__filterEmptyValues(eventTags as EventTags); - var conversionEvent = buildConversionEvent({ + const conversionEvent = buildConversionEvent({ eventKey: eventKey, eventTags: eventTags, userId: userId, @@ -344,7 +344,7 @@ export default class Optimizely { } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); this.errorHandler.handleError(e); - var failedTrackLogMessage = sprintf(LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); + const failedTrackLogMessage = sprintf(LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); this.logger.log(LOG_LEVEL.ERROR, failedTrackLogMessage); } } @@ -357,12 +357,12 @@ export default class Optimizely { */ __emitNotificationCenterTrack(eventKey: string, userId: string, attributes: UserAttributes, eventTags: EventTags): void { try { - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return; } - var conversionEventOptions = { + const conversionEventOptions = { attributes: attributes, clientEngine: this.clientEngine, clientVersion: this.clientVersion, @@ -372,7 +372,7 @@ export default class Optimizely { logger: this.logger, userId: userId, }; - var conversionEvent = getConversionEvent(conversionEventOptions); + const conversionEvent = getConversionEvent(conversionEventOptions); this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.TRACK, { eventKey: eventKey, @@ -385,7 +385,7 @@ export default class Optimizely { this.logger.log(LOG_LEVEL.ERROR, ex.message); this.errorHandler.handleError(ex); } - }; + } /** * Gets variation where visitor will be bucketed. @@ -406,12 +406,12 @@ export default class Optimizely { return null; } - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return null; } - var experiment = configObj.experimentKeyMap[experimentKey]; + const experiment = configObj.experimentKeyMap[experimentKey]; if (!experiment) { this.logger.log( LOG_LEVEL.DEBUG, @@ -420,8 +420,8 @@ export default class Optimizely { return null; } - var variationKey = this.decisionService.getVariation(configObj, experimentKey, userId, attributes); - var decisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) + const variationKey = this.decisionService.getVariation(configObj, experimentKey, userId, attributes); + const decisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) ? DECISION_NOTIFICATION_TYPES.FEATURE_TEST : DECISION_NOTIFICATION_TYPES.AB_TEST; @@ -446,7 +446,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Force a user into a variation for a given experiment. @@ -460,7 +460,7 @@ export default class Optimizely { return false; } - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return false; } @@ -472,7 +472,7 @@ export default class Optimizely { this.errorHandler.handleError(ex); return false; } - }; + } /** * Gets the forced variation for a given user and experiment. @@ -485,7 +485,7 @@ export default class Optimizely { return null; } - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return null; } @@ -497,7 +497,7 @@ export default class Optimizely { this.errorHandler.handleError(ex); return null; } - }; + } /** * Validate string inputs, user attributes and event tags. @@ -524,8 +524,8 @@ export default class Optimizely { delete stringInputs['user_id']; } const inputKeys = Object.keys(stringInputs); - for (var index = 0; index < inputKeys.length; index++) { - var key = inputKeys[index]; + for (let index = 0; index < inputKeys.length; index++) { + const key = inputKeys[index]; if (!stringValidator.validate(stringInputs[key])) { throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, key)); } @@ -545,7 +545,7 @@ export default class Optimizely { return false; } - }; + } /** * Shows failed activation log message and returns null when user is not activated in experiment @@ -562,7 +562,7 @@ export default class Optimizely { ); this.logger.log(LOG_LEVEL.INFO, failedActivationLogMessage); return null; - }; + } /** * Filters out attributes/eventTags with null or undefined values @@ -570,13 +570,13 @@ export default class Optimizely { * @returns {EventTags} */ __filterEmptyValues(map: EventTags): EventTags { - for (var key in map) { + for (const key in map) { if (map.hasOwnProperty(key) && (map[key] === null || map[key] === undefined)) { delete map[key]; } } return map; - }; + } /** * Returns true if the feature is enabled for the given user. @@ -663,7 +663,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return false; } - }; + } /** * Returns an Array containing the keys of all features in the project that are @@ -693,7 +693,7 @@ export default class Optimizely { } if (configObj.featureKeyMap) { objectValues(configObj.featureKeyMap).forEach( - function(this: Optimizely,feature: FeatureFlag): void { + function(this: Optimizely, feature: FeatureFlag): void { if (this.isFeatureEnabled(feature.key, userId, attributes)) { enabledFeatures.push(feature.key); } @@ -707,7 +707,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return []; } - }; + } /** * Returns dynamically-typed value of the variable attached to the given @@ -740,7 +740,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Helper method to get the value for a variable of a certain type attached to a @@ -773,17 +773,17 @@ export default class Optimizely { return null; } - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return null; } - var featureFlag = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); + const featureFlag = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); if (!featureFlag) { return null; } - var variable = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, this.logger); + const variable = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, this.logger); if (!variable) { return null; } @@ -796,10 +796,10 @@ export default class Optimizely { return null; } - var decision = this.decisionService.getVariationForFeature(configObj, featureFlag, userId, attributes); - var featureEnabled = decision.variation !== null ? decision.variation.featureEnabled : false; - var variableValue = this._getFeatureVariableValueFromVariation(featureKey, featureEnabled, decision.variation, variable, userId); - var sourceInfo = {}; + const decision = this.decisionService.getVariationForFeature(configObj, featureFlag, userId, attributes); + const featureEnabled = decision.variation !== null ? decision.variation.featureEnabled : false; + const variableValue = this._getFeatureVariableValueFromVariation(featureKey, featureEnabled, decision.variation, variable, userId); + let sourceInfo = {}; if ( decision.decisionSource === DECISION_SOURCES.FEATURE_TEST && decision.experiment !== null && @@ -826,7 +826,7 @@ export default class Optimizely { }, }); return variableValue; - }; + } /** * Helper method to get the non type-casted value for a variable attached to a @@ -851,14 +851,14 @@ export default class Optimizely { variable: FeatureVariable, userId: string ): string | null { - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return null; } - var variableValue = variable.defaultValue; + let variableValue = variable.defaultValue; if (variation !== null) { - var value = projectConfig.getVariableValueForVariation(configObj, variable, variation, this.logger); + const value = projectConfig.getVariableValueForVariation(configObj, variable, variation, this.logger); if (value !== null) { if (featureEnabled) { variableValue = value; @@ -941,7 +941,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Returns value for the given double variable attached to the given feature @@ -974,7 +974,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Returns value for the given integer variable attached to the given feature @@ -1007,7 +1007,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Returns value for the given string variable attached to the given feature @@ -1040,7 +1040,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Returns value for the given json variable attached to the given feature @@ -1073,7 +1073,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Returns values for all the variables attached to the given feature @@ -1100,25 +1100,25 @@ export default class Optimizely { return null; } - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return null; } - var featureFlag = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); + const featureFlag = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); if (!featureFlag) { return null; } - var decision = this.decisionService.getVariationForFeature(configObj, featureFlag, userId, attributes); - var featureEnabled = decision.variation !== null ? decision.variation.featureEnabled : false; - var allVariables = {}; + const decision = this.decisionService.getVariationForFeature(configObj, featureFlag, userId, attributes); + const featureEnabled = decision.variation !== null ? decision.variation.featureEnabled : false; + const allVariables = {}; featureFlag.variables.forEach(function (this: Optimizely, variable: FeatureVariable) { allVariables[variable.key] = this._getFeatureVariableValueFromVariation(featureKey, featureEnabled, decision.variation, variable, userId); }.bind(this)); - var sourceInfo = {}; + let sourceInfo = {}; if (decision.decisionSource === DECISION_SOURCES.FEATURE_TEST && decision.experiment !== null && decision.variation !== null @@ -1147,11 +1147,11 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Returns OptimizelyConfig object containing experiments and features data - * @return {Object} + * @return {OptimizelyConfig|null} * * OptimizelyConfig Object Schema * { @@ -1185,9 +1185,9 @@ export default class Optimizely { * } * } */ - getOptimizelyConfig() { + getOptimizelyConfig(): OptimizelyConfig | null { try { - var configObj = this.projectConfigManager.getConfig(); + const configObj = this.projectConfigManager.getConfig(); if (!configObj) { return null; } @@ -1197,7 +1197,7 @@ export default class Optimizely { this.errorHandler.handleError(e); return null; } - }; + } /** * Stop background processes belonging to this instance, including: @@ -1232,7 +1232,7 @@ export default class Optimizely { */ close(): Promise<{ success: boolean; reason?: string }> { try { - var eventProcessorStoppedPromise = this.eventProcessor.stop(); + const eventProcessorStoppedPromise = this.eventProcessor.stop(); if (this.__disposeOnUpdate) { this.__disposeOnUpdate(); this.__disposeOnUpdate = null; @@ -1242,7 +1242,7 @@ export default class Optimizely { } Object.keys(this.__readyTimeouts).forEach( function(this: Optimizely, readyTimeoutId: string ) { - var readyTimeoutRecord = this.__readyTimeouts[readyTimeoutId]; + const readyTimeoutRecord = this.__readyTimeouts[readyTimeoutId]; clearTimeout(readyTimeoutRecord.readyTimeout); readyTimeoutRecord.onClose(); }.bind(this) @@ -1269,7 +1269,7 @@ export default class Optimizely { reason: String(err), }); } - }; + } /** * Returns a Promise that fulfills when this instance is ready to use (meaning @@ -1312,22 +1312,22 @@ export default class Optimizely { type Resolve = (value?: unknown) => void; let resolveTimeoutPromise: any; - var timeoutPromise = new Promise(function(resolve: Resolve) { + const timeoutPromise = new Promise(function(resolve: Resolve) { resolveTimeoutPromise = resolve; }); - var timeoutId = this.__nextReadyTimeoutId; + const timeoutId = this.__nextReadyTimeoutId; this.__nextReadyTimeoutId++; - var onReadyTimeout = function(this: Optimizely) { + const onReadyTimeout = function(this: Optimizely) { delete this.__readyTimeouts[timeoutId]; resolveTimeoutPromise({ success: false, reason: sprintf('onReady timeout expired after %s ms', timeoutValue), }); }.bind(this); - var readyTimeout = setTimeout(onReadyTimeout, timeoutValue); - var onClose = function() { + const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); + const onClose = function() { resolveTimeoutPromise({ success: false, reason: 'Instance closed', @@ -1350,5 +1350,5 @@ export default class Optimizely { ); return Promise.race([this.__readyPromise, timeoutPromise]) as Promise<{ success: boolean; reason?: string | undefined; }>; - }; + } } From 36d72c9252bb0bd4aaccaffec2f87fde3d6b064f Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 25 Sep 2020 22:08:05 -0700 Subject: [PATCH 17/22] Update tests --- packages/optimizely-sdk/lib/optimizely/index.tests.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 69d36c572..d4f0f0962 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -23,16 +23,16 @@ import Optimizely from './'; import AudienceEvaluator from '../core/audience_evaluator'; import bluebird from 'bluebird'; import bucketer from '../core/bucketer'; -import projectConfigManager from '../core/project_config/project_config_manager'; +import * as projectConfigManager from '../core/project_config/project_config_manager'; import * as enums from '../utils/enums'; -import eventBuilder from '../core/event_builder/index.js'; +import * as eventBuilder from '../core/event_builder'; import eventDispatcher from '../plugins/event_dispatcher/index.node'; import errorHandler from '../plugins/error_handler'; import * as fns from '../utils/fns'; import logger from '../plugins/logger'; -import decisionService from '../core/decision_service'; +import * as decisionService from '../core/decision_service'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; -import projectConfig from '../core/project_config'; +import * as projectConfig from '../core/project_config'; import testData from '../tests/test_data'; var ERROR_MESSAGES = enums.ERROR_MESSAGES; From fc56d828d19a1eea965d9a170e1ff651cf167bc7 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Fri, 25 Sep 2020 23:08:40 -0700 Subject: [PATCH 18/22] Add missing types --- packages/optimizely-sdk/lib/optimizely/index.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index d6ee0d1b3..dfa50c932 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -60,9 +60,9 @@ const DEFAULT_ONREADY_TIMEOUT = 30000; export default class Optimizely { private __isOptimizelyConfigValid: boolean; private __disposeOnUpdate: (() => void ) | null; - private __readyPromise: Promise<{ success: boolean; reason?: string }>; //TODO - private __readyTimeouts: any;//TODO - private __nextReadyTimeoutId: any;//TODO + private __readyPromise: Promise<{ success: boolean; reason?: string }>; + private __readyTimeouts: { [key: string]: {readyTimeout: number; onClose:() => void} }; + private __nextReadyTimeoutId: number; private clientEngine: string; private clientVersion: string; private errorHandler: ErrorHandler; @@ -1309,10 +1309,8 @@ export default class Optimizely { timeoutValue = DEFAULT_ONREADY_TIMEOUT; } - type Resolve = (value?: unknown) => void; - - let resolveTimeoutPromise: any; - const timeoutPromise = new Promise(function(resolve: Resolve) { + let resolveTimeoutPromise: (value?: unknown) => void; + const timeoutPromise = new Promise(function(resolve: (value?: unknown) => void) { resolveTimeoutPromise = resolve; }); From 1569d178c39c518d1174019d5acb008e2b5362fe Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Sat, 26 Sep 2020 00:34:22 -0700 Subject: [PATCH 19/22] Fix maxQueueSize --- packages/event-processor/src/eventProcessor.ts | 10 +++++++++- packages/optimizely-sdk/lib/optimizely/index.ts | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/event-processor/src/eventProcessor.ts b/packages/event-processor/src/eventProcessor.ts index 59b45abd6..2efd9ed39 100644 --- a/packages/event-processor/src/eventProcessor.ts +++ b/packages/event-processor/src/eventProcessor.ts @@ -16,7 +16,7 @@ // TODO change this to use Managed from js-sdk-models when available import { Managed } from './managed' import { ConversionEvent, ImpressionEvent } from './events' -import { EventV1Request } from './eventDispatcher' +import { EventV1Request, EventDispatcher } from './eventDispatcher' import { EventQueue, DefaultEventQueue, SingleEventQueue } from './eventQueue' import { getLogger } from '@optimizely/js-sdk-logging' import { NOTIFICATION_TYPES, NotificationCenter } from '@optimizely/js-sdk-utils' @@ -34,6 +34,14 @@ export interface EventProcessor extends Managed { process(event: Partial): void } +export interface LogTierV1EventProcessorConfig { + dispatcher: EventDispatcher; + flushInterval?: number; + batchSize?: number; + notificationCenter?: NotificationCenter; + maxQueueSize?: number; +} + export function validateAndGetFlushInterval(flushInterval: number): number { if (flushInterval <= 0) { logger.warn( diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index dfa50c932..d4213008b 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -15,6 +15,7 @@ ***************************************************************************/ import { sprintf, objectValues } from '@optimizely/js-sdk-utils'; import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; +import { LogTierV1EventProcessorConfig } from '../../../event-processor/src'; import * as eventProcessor from '@optimizely/js-sdk-event-processor'; import { FeatureFlag, FeatureVariable } from '../core/project_config/entities'; import { EventDispatcher } from '@optimizely/js-sdk-event-processor'; @@ -139,7 +140,7 @@ export default class Optimizely { batchSize: config.eventBatchSize, maxQueueSize: config.eventMaxQueueSize, // TODO: update event-processor to include maxQueueSize notificationCenter: this.notificationCenter, - }); + } as LogTierV1EventProcessorConfig); const eventProcessorStartedPromise = this.eventProcessor.start(); From 6ebe2f93652dc978d7846d81a266d5f550be5297 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Sat, 26 Sep 2020 00:57:53 -0700 Subject: [PATCH 20/22] Temporary move LogTierV1EventProcessorConfig to shared_types to avoid Travis lint failure --- .../event-processor/src/eventProcessor.ts | 10 +- .../optimizely-sdk/lib/optimizely/index.ts | 166 +++++++++--------- packages/optimizely-sdk/lib/shared_types.ts | 11 ++ 3 files changed, 98 insertions(+), 89 deletions(-) diff --git a/packages/event-processor/src/eventProcessor.ts b/packages/event-processor/src/eventProcessor.ts index 2efd9ed39..59b45abd6 100644 --- a/packages/event-processor/src/eventProcessor.ts +++ b/packages/event-processor/src/eventProcessor.ts @@ -16,7 +16,7 @@ // TODO change this to use Managed from js-sdk-models when available import { Managed } from './managed' import { ConversionEvent, ImpressionEvent } from './events' -import { EventV1Request, EventDispatcher } from './eventDispatcher' +import { EventV1Request } from './eventDispatcher' import { EventQueue, DefaultEventQueue, SingleEventQueue } from './eventQueue' import { getLogger } from '@optimizely/js-sdk-logging' import { NOTIFICATION_TYPES, NotificationCenter } from '@optimizely/js-sdk-utils' @@ -34,14 +34,6 @@ export interface EventProcessor extends Managed { process(event: Partial): void } -export interface LogTierV1EventProcessorConfig { - dispatcher: EventDispatcher; - flushInterval?: number; - batchSize?: number; - notificationCenter?: NotificationCenter; - maxQueueSize?: number; -} - export function validateAndGetFlushInterval(flushInterval: number): number { if (flushInterval <= 0) { logger.warn( diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index d4213008b..1f935f543 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -15,11 +15,16 @@ ***************************************************************************/ import { sprintf, objectValues } from '@optimizely/js-sdk-utils'; import { LogHandler, ErrorHandler } from '@optimizely/js-sdk-logging'; -import { LogTierV1EventProcessorConfig } from '../../../event-processor/src'; import * as eventProcessor from '@optimizely/js-sdk-event-processor'; import { FeatureFlag, FeatureVariable } from '../core/project_config/entities'; import { EventDispatcher } from '@optimizely/js-sdk-event-processor'; -import { UserAttributes, Variation, EventTags, OptimizelyConfig } from '../shared_types'; +import { + UserAttributes, + Variation, + EventTags, + OptimizelyConfig, + LogTierV1EventProcessorConfig +} from '../shared_types'; import { createProjectConfigManager, ProjectConfigManager } from '../core/project_config/project_config_manager'; import { createNotificationCenter, NotificationCenter } from '../core/notification_center'; import { createDecisionService, DecisionService } from '../core/decision_service'; @@ -77,88 +82,87 @@ export default class Optimizely { constructor(config: projectConfig.ProjectConfig) { let clientEngine = config.clientEngine; - if (enums.VALID_CLIENT_ENGINES.indexOf(clientEngine) === -1) { - config.logger.log( - LOG_LEVEL.INFO, - sprintf(LOG_MESSAGES.INVALID_CLIENT_ENGINE, MODULE_NAME, clientEngine) - ); - clientEngine = enums.NODE_CLIENT_ENGINE; - } - - this.clientEngine = clientEngine; - this.clientVersion = config.clientVersion || enums.NODE_CLIENT_VERSION; - this.errorHandler = config.errorHandler; - this.eventDispatcher = config.eventDispatcher; - this.__isOptimizelyConfigValid = config.isValidInstance; - this.logger = config.logger; - - this.projectConfigManager = createProjectConfigManager({ - datafile: config.datafile, - datafileOptions: config.datafileOptions, - jsonSchemaValidator: config.jsonSchemaValidator, - sdkKey: config.sdkKey, - }); - - this.__disposeOnUpdate = this.projectConfigManager.onUpdate( - function(this: Optimizely, configObj: projectConfig.ProjectConfig) { - this.logger.log( + if (enums.VALID_CLIENT_ENGINES.indexOf(clientEngine) === -1) { + config.logger.log( LOG_LEVEL.INFO, - sprintf(LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, MODULE_NAME, configObj.revision, configObj.projectId) + sprintf(LOG_MESSAGES.INVALID_CLIENT_ENGINE, MODULE_NAME, clientEngine) ); - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); - }.bind(this) - ); + clientEngine = enums.NODE_CLIENT_ENGINE; + } - const projectConfigManagerReadyPromise = this.projectConfigManager.onReady(); + this.clientEngine = clientEngine; + this.clientVersion = config.clientVersion || enums.NODE_CLIENT_VERSION; + this.errorHandler = config.errorHandler; + this.eventDispatcher = config.eventDispatcher; + this.__isOptimizelyConfigValid = config.isValidInstance; + this.logger = config.logger; + + this.projectConfigManager = createProjectConfigManager({ + datafile: config.datafile, + datafileOptions: config.datafileOptions, + jsonSchemaValidator: config.jsonSchemaValidator, + sdkKey: config.sdkKey, + }); - let userProfileService = null; - if (config.userProfileService) { - try { - if (userProfileServiceValidator.validate(config.userProfileService)) { - userProfileService = config.userProfileService; - this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.VALID_USER_PROFILE_SERVICE, MODULE_NAME)); + this.__disposeOnUpdate = this.projectConfigManager.onUpdate( + function(this: Optimizely, configObj: projectConfig.ProjectConfig) { + this.logger.log( + LOG_LEVEL.INFO, + sprintf(LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, MODULE_NAME, configObj.revision, configObj.projectId) + ); + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + }.bind(this) + ); + + const projectConfigManagerReadyPromise = this.projectConfigManager.onReady(); + + let userProfileService = null; + if (config.userProfileService) { + try { + if (userProfileServiceValidator.validate(config.userProfileService)) { + userProfileService = config.userProfileService; + this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.VALID_USER_PROFILE_SERVICE, MODULE_NAME)); + } + } catch (ex) { + this.logger.log(LOG_LEVEL.WARNING, ex.message); } - } catch (ex) { - this.logger.log(LOG_LEVEL.WARNING, ex.message); } - } - this.decisionService = createDecisionService({ - userProfileService: userProfileService, - logger: this.logger, - UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators, - }); - - this.notificationCenter = createNotificationCenter({ - logger: this.logger, - errorHandler: this.errorHandler, - }); - - this.eventProcessor = new eventProcessor.LogTierV1EventProcessor({ - dispatcher: this.eventDispatcher, - flushInterval: config.eventFlushInterval, - batchSize: config.eventBatchSize, - maxQueueSize: config.eventMaxQueueSize, // TODO: update event-processor to include maxQueueSize - notificationCenter: this.notificationCenter, - } as LogTierV1EventProcessorConfig); - - - const eventProcessorStartedPromise = this.eventProcessor.start(); - - this.__readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then(function(promiseResults) { - // Only return status from project config promise because event processor promise does not return any status. - return promiseResults[0]; - }) - - this.__readyTimeouts = {}; - this.__nextReadyTimeoutId = 0; + this.decisionService = createDecisionService({ + userProfileService: userProfileService, + logger: this.logger, + UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators, + }); + + this.notificationCenter = createNotificationCenter({ + logger: this.logger, + errorHandler: this.errorHandler, + }); + + this.eventProcessor = new eventProcessor.LogTierV1EventProcessor({ + dispatcher: this.eventDispatcher, + flushInterval: config.eventFlushInterval, + batchSize: config.eventBatchSize, + maxQueueSize: config.eventMaxQueueSize, // TODO: update event-processor to include maxQueueSize + notificationCenter: this.notificationCenter, + } as LogTierV1EventProcessorConfig); + + const eventProcessorStartedPromise = this.eventProcessor.start(); + + this.__readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then(function(promiseResults) { + // Only return status from project config promise because event processor promise does not return any status. + return promiseResults[0]; + }) + + this.__readyTimeouts = {}; + this.__nextReadyTimeoutId = 0; } /** * Returns a truthy value if this instance currently has a valid project config * object, and the initial configuration object that was passed into the * constructor was also valid. - * @return {*} + * @return {boolean} */ __isValidInstance(): boolean { return this.__isOptimizelyConfigValid && !!this.projectConfigManager.getConfig(); @@ -451,10 +455,11 @@ export default class Optimizely { /** * Force a user into a variation for a given experiment. - * @param {string} experimentKey - * @param {string} userId - * @param {string|null} variationKey user will be forced into. If null, then clear the existing experiment-to-variation mapping. - * @return {boolean} A boolean value that indicates if the set completed successfully. + * @param {string} experimentKey + * @param {string} userId + * @param {string|null} variationKey user will be forced into. If null, + * then clear the existing experiment-to-variation mapping. + * @return {boolean} A boolean value that indicates if the set completed successfully. */ setForcedVariation(experimentKey: string, userId: string, variationKey: string | null): boolean { if (!this.__validateInputs({ experiment_key: experimentKey, user_id: userId })) { @@ -477,8 +482,8 @@ export default class Optimizely { /** * Gets the forced variation for a given user and experiment. - * @param {string} experimentKey - * @param {string} userId + * @param {string} experimentKey + * @param {string} userId * @return {string|null} The forced variation key. */ getForcedVariation(experimentKey: string, userId: string): string | null { @@ -505,7 +510,7 @@ export default class Optimizely { * @param {unknown} stringInputs Map of string keys and associated values * @param {unknown} userAttributes Optional parameter for user's attributes * @param {unknown} eventTags Optional parameter for event tags - * @return {boolean} True if inputs are valid + * @return {boolean} True if inputs are valid * */ __validateInputs( @@ -720,8 +725,9 @@ export default class Optimizely { * being accessed * @param {string} userId ID for the user * @param {UserAttributes} attributes Optional user attributes - * @return {unknown} Value of the variable cast to the appropriate - * type, or null if the feature key is invalid or the variable key is invalid + * @return {unknown} Value of the variable cast to the appropriate + * type, or null if the feature key is invalid or + * the variable key is invalid */ getFeatureVariable( diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index 7c5e757fa..e4c8cdbf8 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -120,3 +120,14 @@ export interface OptimizelyConfig { revision: string; getDatafile(): string; } + +/** + * Temprorary location LogTierV1EventProcessorConfig + */ +export interface LogTierV1EventProcessorConfig { + dispatcher: import ('@optimizely/js-sdk-event-processor').EventDispatcher; + flushInterval?: number; + batchSize?: number; + notificationCenter?: import('./core/notification_center').NotificationCenter; + maxQueueSize?: number; +} From e08bfab138d6957b358c1fbdfe898136550c1382 Mon Sep 17 00:00:00 2001 From: Polina Nguen Date: Sat, 26 Sep 2020 01:13:44 -0700 Subject: [PATCH 21/22] fix Lint --- packages/event-processor/src/eventProcessor.ts | 2 +- packages/optimizely-sdk/lib/optimizely/index.ts | 4 ++-- packages/optimizely-sdk/lib/shared_types.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/event-processor/src/eventProcessor.ts b/packages/event-processor/src/eventProcessor.ts index 59b45abd6..57826e41e 100644 --- a/packages/event-processor/src/eventProcessor.ts +++ b/packages/event-processor/src/eventProcessor.ts @@ -31,7 +31,7 @@ export type ProcessableEvent = ConversionEvent | ImpressionEvent export type EventDispatchResult = { result: boolean; event: ProcessableEvent } export interface EventProcessor extends Managed { - process(event: Partial): void + process(event: ProcessableEvent): void } export function validateAndGetFlushInterval(flushInterval: number): number { diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/packages/optimizely-sdk/lib/optimizely/index.ts index 1f935f543..e7292a6e6 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/packages/optimizely-sdk/lib/optimizely/index.ts @@ -255,7 +255,7 @@ export default class Optimizely { configObj: configObj, }); // TODO is it okay to not pass a projectConfig as second argument - this.eventProcessor.process(impressionEvent); + this.eventProcessor.process(impressionEvent as eventProcessor.ProcessableEvent); this.__emitNotificationCenterActivate(experimentKey, variationKey, userId, attributes); } @@ -344,7 +344,7 @@ export default class Optimizely { }); this.logger.log(LOG_LEVEL.INFO, sprintf(enums.LOG_MESSAGES.TRACK_EVENT, MODULE_NAME, eventKey, userId)); // TODO is it okay to not pass a projectConfig as second argument - this.eventProcessor.process(conversionEvent); + this.eventProcessor.process(conversionEvent as eventProcessor.ProcessableEvent); this.__emitNotificationCenterTrack(eventKey, userId, attributes as UserAttributes, eventTags); } catch (e) { this.logger.log(LOG_LEVEL.ERROR, e.message); diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/packages/optimizely-sdk/lib/shared_types.ts index e4c8cdbf8..b95b50020 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/packages/optimizely-sdk/lib/shared_types.ts @@ -122,7 +122,7 @@ export interface OptimizelyConfig { } /** - * Temprorary location LogTierV1EventProcessorConfig + * Temprorary placement of LogTierV1EventProcessorConfig */ export interface LogTierV1EventProcessorConfig { dispatcher: import ('@optimizely/js-sdk-event-processor').EventDispatcher; From 170b26e21cc302d60b568afc29df69b8e51a340a Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Mon, 28 Sep 2020 19:23:08 +0500 Subject: [PATCH 22/22] index.browser.js converted to ts --- .../{index.browser.js => index.browser.ts} | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) rename packages/optimizely-sdk/lib/{index.browser.js => index.browser.ts} (77%) diff --git a/packages/optimizely-sdk/lib/index.browser.js b/packages/optimizely-sdk/lib/index.browser.ts similarity index 77% rename from packages/optimizely-sdk/lib/index.browser.js rename to packages/optimizely-sdk/lib/index.browser.ts index 8a4286ec5..8245627c7 100644 --- a/packages/optimizely-sdk/lib/index.browser.js +++ b/packages/optimizely-sdk/lib/index.browser.ts @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { +import { getLogger, setLogHandler, setLogLevel, setErrorHandler, getErrorHandler, LogLevel, + ErrorHandler, + LogHandler, } from '@optimizely/js-sdk-logging'; -import { LocalStoragePendingEventsDispatcher } from '@optimizely/js-sdk-event-processor'; +import { LocalStoragePendingEventsDispatcher } from '@optimizely/js-sdk-event-processor'; import { assign } from './utils/fns'; import * as configValidator from './utils/config_validator'; import defaultErrorHandler from './plugins/error_handler'; @@ -31,32 +33,37 @@ import * as enums from './utils/enums'; import loggerPlugin from './plugins/logger'; import Optimizely from './optimizely'; import eventProcessorConfigValidator from './utils/event_processor_config_validator'; +import * as projectConfig from './core/project_config'; -var logger = getLogger(); +const logger = getLogger(); setLogHandler(loggerPlugin.createLogger()); setLogLevel(LogLevel.INFO); -var MODULE_NAME = 'INDEX_BROWSER'; -var DEFAULT_EVENT_BATCH_SIZE = 10; -var DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s - -var hasRetriedEvents = false; +const MODULE_NAME = 'INDEX_BROWSER'; +const DEFAULT_EVENT_BATCH_SIZE = 10; +const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s + +let hasRetriedEvents = false; + +interface Config { + datafile?: projectConfig.ProjectConfig; + errorHandler?: ErrorHandler; + eventDispatcher?: (...args: unknown[]) => unknown; + logger?: LogHandler; + logLevel?: LogLevel; + userProfileService?: import('./shared_types').UserProfileService; + eventBatchSize?: number; + eventFlushInterval?: number; + sdkKey?: string; + isValidInstance?: boolean; +} /** * Creates an instance of the Optimizely class - * @param {Object} config - * @param {Object|string} config.datafile - * @param {Object} config.errorHandler - * @param {Object} config.eventDispatcher - * @param {Object} config.logger - * @param {Object} config.logLevel - * @param {Object} config.userProfileService - * @param {Object} config.eventBatchSize - * @param {Object} config.eventFlushInterval - * @param {string} config.sdkKey - * @return {Object} the Optimizely object + * @param {Config} config + * @return {Optimizely} the Optimizely object */ -var createInstance = function(config) { +const createInstance = function (config: Config): Optimizely | null { try { config = config || {}; @@ -81,7 +88,7 @@ var createInstance = function(config) { config.isValidInstance = false; } - var eventDispatcher; + let eventDispatcher; // prettier-ignore if (config.eventDispatcher == null) { // eslint-disable-line eqeqeq // only wrap the event dispatcher with pending events retry if the user didnt override @@ -97,7 +104,7 @@ var createInstance = function(config) { eventDispatcher = config.eventDispatcher; } - config = assign( + const optimizelyConfig = assign( { clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, eventBatchSize: DEFAULT_EVENT_BATCH_SIZE, @@ -110,11 +117,11 @@ var createInstance = function(config) { logger: logger, errorHandler: getErrorHandler(), } - ); + ) as projectConfig.ProjectConfig; if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - config.eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; + optimizelyConfig.eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; } if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { logger.warn( @@ -122,17 +129,17 @@ var createInstance = function(config) { config.eventFlushInterval, DEFAULT_EVENT_FLUSH_INTERVAL ); - config.eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; + optimizelyConfig.eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; } - var optimizely = new Optimizely(config); + const optimizely = new Optimizely(optimizelyConfig); try { if (typeof window.addEventListener === 'function') { - var unloadEvent = 'onpagehide' in window ? 'pagehide' : 'unload'; + const unloadEvent = 'onpagehide' in window ? 'pagehide' : 'unload'; window.addEventListener( unloadEvent, - function() { + function () { optimizely.close(); }, false @@ -149,7 +156,7 @@ var createInstance = function(config) { } }; -var __internalResetRetryState = function() { +const __internalResetRetryState = function (): void { hasRetriedEvents = false; }; @@ -164,8 +171,8 @@ export { setLogHandler as setLogger, setLogLevel, createInstance, - __internalResetRetryState, -} + __internalResetRetryState, +}; export default { logging: loggerPlugin,