UNPKG

@optimizely/optimizely-sdk

Version:

JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts

287 lines (286 loc) 18.2 kB
/** * Copyright 2017-2022, 2024-2026, 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 { LoggerFacade } from '../../logging/logger'; import { DecisionSource } from '../../utils/enums'; import { ProjectConfig } from '../../project_config/project_config'; import { DecisionResponse, Experiment, FeatureFlag, Holdout, OptimizelyDecideOption, OptimizelyUserContext, UserProfileService, UserProfileServiceAsync, Variation } from '../../shared_types'; import { CmabService } from './cmab/cmab_service'; import { OpType } from '../../utils/type'; import { Value } from '../../utils/promise/operation_value'; import { Platform } from '../../platform_support'; export declare const EXPERIMENT_NOT_RUNNING = "Experiment %s is not running."; export declare const RETURNING_STORED_VARIATION = "Returning previously activated variation \"%s\" of experiment \"%s\" for user \"%s\" from user profile."; export declare const USER_NOT_IN_EXPERIMENT = "User %s does not meet conditions to be in experiment %s."; export declare const USER_HAS_NO_VARIATION = "User %s is in no variation of experiment %s."; export declare const USER_HAS_VARIATION = "User %s is in variation %s of experiment %s."; export declare const USER_FORCED_IN_VARIATION = "User %s is forced in variation %s."; export declare const FORCED_BUCKETING_FAILED = "Variation key %s is not in datafile. Not activating user %s."; export declare const EVALUATING_AUDIENCES_COMBINED = "Evaluating audiences for %s \"%s\": %s."; export declare const AUDIENCE_EVALUATION_RESULT_COMBINED = "Audiences for %s %s collectively evaluated to %s."; export declare const USER_IN_ROLLOUT = "User %s is in rollout of feature %s."; export declare const USER_NOT_IN_ROLLOUT = "User %s is not in rollout of feature %s."; export declare const FEATURE_HAS_NO_EXPERIMENTS = "Feature %s is not attached to any experiments."; export declare const USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE = "User %s does not meet conditions for targeting rule %s."; export declare const USER_NOT_BUCKETED_INTO_TARGETING_RULE = "User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule."; export declare const USER_BUCKETED_INTO_TARGETING_RULE = "User %s bucketed into targeting rule %s."; export declare const NO_ROLLOUT_EXISTS = "There is no rollout of feature %s."; export declare const INVALID_ROLLOUT_ID = "Invalid rollout ID %s attached to feature %s"; export declare const ROLLOUT_HAS_NO_EXPERIMENTS = "Rollout of feature %s has no experiments"; export declare const IMPROPERLY_FORMATTED_EXPERIMENT = "Experiment key %s is improperly formatted."; export declare const USER_HAS_FORCED_VARIATION = "Variation %s is mapped to experiment %s and user %s in the forced variation map."; export declare const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = "User %s meets conditions for targeting rule %s."; export declare const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED = "Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map."; export declare const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED = "Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map."; export declare const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = "Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map."; export declare const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = "Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map."; export declare const CMAB_NOT_SUPPORTED_IN_SYNC = "CMAB is not supported in sync mode."; export declare const CMAB_FETCH_FAILED = "Failed to fetch CMAB data for experiment %s."; export declare const CMAB_FETCHED_VARIATION_INVALID = "Fetched variation %s for cmab experiment %s is invalid."; export declare const HOLDOUT_NOT_RUNNING = "Holdout %s is not running."; export declare const USER_MEETS_CONDITIONS_FOR_HOLDOUT = "User %s meets conditions for holdout %s."; export declare const USER_DOESNT_MEET_CONDITIONS_FOR_HOLDOUT = "User %s does not meet conditions for holdout %s."; export declare const USER_BUCKETED_INTO_HOLDOUT_VARIATION = "User %s is in variation %s of holdout %s."; export declare const USER_NOT_BUCKETED_INTO_HOLDOUT_VARIATION = "User %s is in no holdout variation."; export interface DecisionObj { experiment: Experiment | Holdout | null; variation: Variation | null; decisionSource: DecisionSource; cmabUuid?: string; } interface DecisionServiceOptions { userProfileService?: UserProfileService; userProfileServiceAsync?: UserProfileServiceAsync; logger?: LoggerFacade; UNSTABLE_conditionEvaluators: unknown; cmabService: CmabService; } type VarationKeyWithCmabParams = { variationKey?: string; cmabUuid?: string; }; export type DecisionReason = [string, ...any[]]; export type VariationResult = DecisionResponse<VarationKeyWithCmabParams>; export type DecisionResult = DecisionResponse<DecisionObj>; export type DecideOptionsMap = Partial<Record<OptimizelyDecideOption, boolean>>; export declare const CMAB_DUMMY_ENTITY_ID = "$"; export declare const LOGGER_NAME = "DecisionService"; /** * Optimizely's decision service that determines which variation of an experiment the user will be allocated to. * * The decision service contains all logic around how a user decision is made. This includes all of the following (in order): * 1. Checking experiment status * 2. Checking forced bucketing * 3. Checking whitelisting * 4. Checking user profile service for past bucketing decisions (sticky bucketing) * 5. Checking audience targeting * 6. Using Murmurhash3 to bucket the user. * * @constructor * @param {DecisionServiceOptions} options * @returns {DecisionService} */ export declare class DecisionService { private logger?; private audienceEvaluator; private forcedVariationMap; private userProfileService?; private userProfileServiceAsync?; private cmabService; constructor(options: DecisionServiceOptions); private isCmab; /** * Resolves the variation into which the visitor will be bucketed. * * @param {ProjectConfig} configObj - The parsed project configuration object. * @param {Experiment} experiment - The experiment for which the variation is being resolved. * @param {OptimizelyUserContext} user - The user context associated with this decision. * @returns {DecisionResponse<string|null>} - A DecisionResponse containing the variation the user is bucketed into, * along with the decision reasons. */ private resolveVariation; private getDecisionForCmabExperiment; private getDecisionFromBucketer; /** * Gets variation where visitor will be bucketed. * @param {ProjectConfig} configObj The parsed project configuration object * @param {Experiment} experiment * @param {OptimizelyUserContext} user A user context * @param {[key: string]: boolean} options Optional map of decide options * @return {DecisionResponse<string|null>} DecisionResponse containing the variation the user is bucketed into * and the decide reasons. */ getVariation(configObj: ProjectConfig, experiment: Experiment, user: OptimizelyUserContext, options?: DecideOptionsMap): DecisionResponse<string | null>; /** * Merges attributes from attributes[STICKY_BUCKETING_KEY] and userProfileService * @param {string} userId * @param {UserAttributes} attributes * @return {ExperimentBucketMap} finalized copy of experiment_bucket_map */ private resolveExperimentBucketMap; /** * Checks if user is whitelisted into any variation and return that variation if so * @param {Experiment} experiment * @param {string} userId * @return {DecisionResponse<Variation|null>} DecisionResponse containing the forced variation if it exists * or user ID and the decide reasons. */ private getWhitelistedVariation; /** * Checks whether the user is included in experiment audience * @param {ProjectConfig} configObj The parsed project configuration object * @param {string} experimentKey Key of experiment being validated * @param {string} evaluationAttribute String representing experiment key or rule * @param {string} userId ID of user * @param {UserAttributes} attributes Optional parameter for user's attributes * @param {string} loggingKey String representing experiment key or rollout rule. To be used in log messages only. * @return {DecisionResponse<boolean>} DecisionResponse DecisionResponse containing result true if user meets audience conditions and * the decide reasons. */ private checkIfUserIsInAudience; /** * Given an experiment key and user ID, returns params used in bucketer call * @param {ProjectConfig} configObj The parsed project configuration object * @param {string} experimentKey Experiment key used for bucketer * @param {string} bucketingId ID to bucket user into * @param {string} userId ID of user to be bucketed * @return {BucketerParams} */ private buildBucketerParams; /** * Determines if a user should be bucketed into a holdout variation. * @param {ProjectConfig} configObj - The parsed project configuration object. * @param {Holdout} holdout - The holdout to evaluate. * @param {OptimizelyUserContext} user - The user context. * @returns {DecisionResponse<DecisionObj>} - DecisionResponse containing holdout decision and reasons. */ private getVariationForHoldout; /** * Pull the stored variation out of the experimentBucketMap for an experiment/userId * @param {ProjectConfig} configObj The parsed project configuration object * @param {Experiment} experiment * @param {string} userId * @param {ExperimentBucketMap} experimentBucketMap mapping experiment => { variation_id: <variationId> } * @return {Variation|null} the stored variation or null if the user profile does not have one for the given experiment */ private getStoredVariation; /** * Get the user profile with the given user ID * @param {string} userId * @return {UserProfile} the stored user profile or an empty profile if one isn't found or error */ private getUserProfile; private updateUserProfile; /** * Saves the bucketing decision to the user profile * @param {Experiment} experiment * @param {Variation} variation * @param {string} userId * @param {ExperimentBucketMap} experimentBucketMap */ private saveUserProfile; /** * Determines variations for the specified feature flags. * * @param {ProjectConfig} configObj - The parsed project configuration object. * @param {FeatureFlag[]} featureFlags - The feature flags for which variations are to be determined. * @param {OptimizelyUserContext} user - The user context associated with this decision. * @param {Record<string, boolean>} options - An optional map of decision options. * @returns {DecisionResponse<DecisionObj>[]} - An array of DecisionResponse containing objects with * experiment, variation, decisionSource properties, and decision reasons. */ getVariationsForFeatureList(configObj: ProjectConfig, featureFlags: FeatureFlag[], user: OptimizelyUserContext, options?: DecideOptionsMap): DecisionResult[]; resolveVariationsForFeatureList<OP extends OpType>(op: OP, configObj: ProjectConfig, featureFlags: FeatureFlag[], user: OptimizelyUserContext, options: DecideOptionsMap): Value<OP, DecisionResult[]>; private resolveVariationForFlag; /** * Given a feature, user ID, and attributes, returns a decision response containing * an object representing a decision and decide reasons. If the user was bucketed into * a variation for the given feature and attributes, the 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 {OptimizelyUserContext} user A user context * @param {[key: string]: boolean} options Map of decide options * @return {DecisionResponse} DecisionResponse DecisionResponse containing an object with experiment, variation, and decisionSource * properties and decide reasons. If the user was not bucketed into a variation, the variation * property in decision object is null. */ getVariationForFeature(configObj: ProjectConfig, feature: FeatureFlag, user: OptimizelyUserContext, options?: DecideOptionsMap): DecisionResponse<DecisionObj>; private getVariationForFeatureExperiment; private traverseFeatureExperimentList; private getVariationForRollout; /** * Get bucketing Id from user attributes. * @param {string} userId * @param {UserAttributes} attributes * @returns {string} Bucketing Id if it is a string type in attributes, user Id otherwise. */ private getBucketingId; /** * Finds a validated forced decision for specific flagKey and optional ruleKey. * @param {ProjectConfig} config A projectConfig. * @param {OptimizelyUserContext} user A Optimizely User Context. * @param {string} flagKey A flagKey. * @param {ruleKey} ruleKey A ruleKey (optional). * @return {DecisionResponse<Variation|null>} DecisionResponse object containing valid variation object and decide reasons. */ findValidatedForcedDecision(config: ProjectConfig, user: OptimizelyUserContext, flagKey: string, ruleKey?: string): DecisionResponse<Variation | null>; /** * Removes forced variation for given userId and experimentKey * @param {string} 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 */ private removeForcedVariation; /** * Sets forced variation for given userId and experimentKey * @param {string} userId String representing the user id * @param {string} experimentId Number representing the experiment id * @param {number} variationId Number representing the variation id * @throws If the user id is not valid */ private setInForcedVariationMap; /** * 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 {DecisionResponse<string|null>} DecisionResponse containing variation which the given user and experiment * should be forced into and the decide reasons. */ getForcedVariation(configObj: ProjectConfig, experimentKey: string, userId: string): DecisionResponse<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 {string|null} 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: string | null): boolean; private getVariationFromExperimentRule; private getVariationFromDeliveryRule; } /** * Creates an instance of the DecisionService. * @param {DecisionServiceOptions} options Configuration options * @return {Object} An instance of the DecisionService */ export declare function createDecisionService(options: DecisionServiceOptions): DecisionService; export declare const __platforms: Platform[]; export {};