import { UpGradeClientInterfaces } from '../types'; import { ILogInput, CaliperEnvelope, IExperimentAssignmentv5, MARKED_DECISION_POINT_STATUS, IUserAliases, BinaryRewardAllowedValue } from 'upgrade_types'; import Assignment from '../Assignment/Assignment'; /** * UpGradeClient is the main class for interacting with the UpGrade API. * * @example * ```typescript * import UpgradeClient from 'upgrade_client_lib/dist/browser'; * ``` * * ```typescript * import UpgradeClient from 'upgrade_client_lib/dist/node'; * ``` * * General UpGrade types can also be accessed as named exports: * ```typescript * import UpgradeClient, { IExperimentAssignment } from 'upgrade_client_lib/dist/browser'; * ``` * * SDK-Specific types can be accessed also: * ```typescript * import { UpGradeClientInterfaces } from 'upgrade_client_lib/dist/clientlibs/js/src/identifiers'; * * const initResponse: UpGradeClientInterfaces.IUser = await upgradeClient.init(); * ``` */ export default class UpgradeClient { private apiService; private dataService; static MARKED_DECISION_POINT_STATUS: typeof MARKED_DECISION_POINT_STATUS; static BINARY_REWARD_VALUE: typeof BinaryRewardAllowedValue; /** * When constructing UpgradeClient, the user id, api host url, and "context" identifier are required. * These will be attached to various API calls for this instance of the client. * * @example * * ```typescript * // required * const hostUrl: "htts://my-hosted-upgrade-api.com"; * const userId: "abc123"; * const context: "my-app-context-name"; * * // not required, each is also optional * const options: { * token: "someToken"; * clientSessionId: "someSessionId"; * featureFlagUserGroupsForSession: null * } * * const upgradeClient: UpgradeClient[] = new UpgradeClient(hostURL, userId, context); * const upgradeClient: UpgradeClient[] = new UpgradeClient(hostURL, userId, context, options); * ``` * * UPDATE: #featureFlagUserGroupsForSession * * ```typescript * // required * const hostUrl: "htts://my-hosted-upgrade-api.com"; * const userId: "abc123"; * const context: "my-app-context-name"; * * // to configure feature flag endpoint to rely on session-only groups or merge supplemental groups with stored user groups * // see below for usage scenarios * // note: this is optional, and if not provided, the client will use standard user lookup with stored groups only * const options: { * featureFlagUserGroupsForSession: { * groupsForSession: { "classId": ["testClass"] }; * includeStoredUserGroups: false; // true to merge with stored user groups, false to skip any stored user entirely * } * } * * const upgradeClient: UpgradeClient[] = new UpgradeClient(hostURL, userId, context); * const upgradeClient: UpgradeClient[] = new UpgradeClient(hostURL, userId, context, options); * ``` * * **Stored-user Mode** (Standard stored user lookup): * - Omit both `groupsForSession` and `includeStoredUserGroups` parameters * - Uses only stored user groups from the database * - User must already have been initialized, will 404 if user does not exist * * **Ephemeral Mode** (Session-only groups): * - Set `includeStoredUserGroups` to `false` and provide `groupsForSession` * - Uses only the groups provided in the session, ignoring any stored user groups. * - Does not require the user to be initialized (it will bypass stored user lookup) * - Useful when complete group information is always provided at runtime. * * **Merged Mode** (Stored + Session groups): * - Set `includeStoredUserGroups` to `true` and provide `groupsForSession` * - User must already have been initialized, will 404 if user does not exist. * - Session groups are merged with stored groups if they don't already exist for stored user. * - Session groups are never persisted. * - Useful for adding context-specific ephemeral groups to an existing user. */ constructor(userId: string, hostUrl: string, context: string, options?: UpGradeClientInterfaces.IConfigOptions); private validateFeatureFlagGroupOptions; /** * Sets the feature flag session user group options. * * Note: This is a convenience method, this can also be set directly in the constructor of UpgradeClient. * See example usage in the constructor documentation. * * @example * ```typescript * * **Scenario 1: Session-only groups (Ephemeral user request)** * const options: UpGradeClientInterfaces.IFeatureFlagOptions = { * groupsForSession: { classId: ['testClass'] }, * includeStoredUserGroups: false * }; * ``` * * **Scenario 2: Merged groups (Merged stored/ephemeral groups request mode)** * ```typescript * const options: UpGradeClientInterfaces.IFeatureFlagOptions = { * groupsForSession: { classId: ['testClass'] }, * includeStoredUserGroups: true * }; * ``` * * **Scenario 3: Default behavior (Standard mode)** * Note this is the default behavior and does not need to be set, unless clearing previously set groupsForSession options * ```typescript * const options: UpGradeClientInterfaces.IFeatureFlagOptions = null; * ``` */ setFeatureFlagUserGroupsForSession(featureFlagOptions: UpGradeClientInterfaces.IFeatureFlagOptions | null | undefined): void; /** * This will initialize user and metadata for the user. It will return the user object with id, group, and working group. * NOTE: A user must be initialized at least once before calling any other methods. * Else, you will see "Experiment user not defined" errors when other SDK methods are called. * * @example * ```typescript * const group: Record> = { * classId: ['class1', 'class2'], * districtId: ['district1', 'district2'], * } * * const workingGroup: Record = { * classId: 'class1', * districtId: 'district2', * } * * const initResponse: UpGradeClientInterfaces.IUser[] = await upgradeClient.init(); * const initResponse: UpGradeClientInterfaces.IUser[] = await upgradeClient.init(group); * const initResponse: UpGradeClientInterfaces.IUser[] = await upgradeClient.init(group, workingGroup); * * ``` */ init(group?: Record>, workingGroup?: Record): Promise; /** * Will set the group membership(s) for the user and return the user object with updated working group. * * @example * ```typescript * const group: Record> = { * classId: ['class1', 'class2'], * districtId: ['district1', 'district2'], * } * * const groupMembershipResponse: UpGradeClientInterfaces.IUser[] = await upgradeClient.setGroupMembership(group); * ``` */ setGroupMembership(group: Record>): Promise; /** * Will set the working group(s) for the user and return the user object with updated working group. * * @example * ```typescript * const workingGroup: Record = { * classId: 'class1', * districtId: 'district2', * } * * const workingGroupResponse: UpGradeClientInterfaces.IUser[] = await upgradeClient.setWorkingGroup(workingGroup); * ``` */ setWorkingGroup(workingGroup: Record): Promise; /** * This will return all the assignment for the given context. * The return object contains site, target, experimentType, assignedCondition array and assignedFactor array(optional) * Here assignedCondition and assignedFactors(For Factorial-experiment) are arrays * They will return a stack of condition user will be assigned in that order * For With-in subjects these stacks will be contain all conditions according to the chosen `Condition-Order` * For Between subjects experiment both stack will return array containing single condition. * @param options.ignoreCache If true, it will ignore the cached experiment assignments and fetch fresh data from the API. * This is useful when you want to ensure you have the latest assignments. * If false, it will return the cached assignments if available. * @example * ```typescript * const userId = "User1" * const context = "mathia" * * const getAllResponse: IExperimentAssignmentv5[] = await upgradeClient.getAllExperimentConditions(); * ``` */ getAllExperimentConditions(options?: { ignoreCache: boolean; }): Promise; /** * Given a site and optional target, return the Assignment this decision point * NOTE: If getAllExperimentConditions() has not been called, this will call it first. * NOTE ALSO: If getAllExperimentConditions() has been called, this will return the cached result and not make a network call. * * @example * ```typescript * const assignmentResponse: Assignment = await upgradeClient.getDecisionPointAssignment(site, target); * ``` */ getDecisionPointAssignment(site: string, target?: string): Promise; /** * Will record ("mark") that a user has "seen" a condition at the given decision point (site + target). * * NOTE: This method may be deprecated in favor of Assignment.markDecisionPoint() in a future release. * * Marking the decision point will record the user's condition assignment, regardless of whether the user is enrolled in an experiment. * * @param site * @param target * @param condition `condition` is the string identifier that the user was assigned to. If none is provided, the condition will be default (null) * * @param status `status` signifies a client application's note on what it did in the code with condition assignment that Upgrade provided. * Status can be one of the following: * * ```ts * export enum MARKED_DECISION_POINT_STATUS { * CONDITION_APPLIED = 'condition applied', * CONDITION_FAILED_TO_APPLY = 'condition not applied', * NO_CONDITION_ASSIGNED = 'no condition assigned', * } * ``` * * @param uniquifier A `uniquifier` unique string can be sent along to help tie a user's logged metrics to a specific marked condition. * This identifier will also need to be sent when calling `upgradeClient.log()` * This is required for 'within-subjects' experiments. * * @param clientError The client can also send along an additional `clientError` string to log context as to why a condition was not applied. * * @example * ```ts * import { MARKED_DECISION_POINT_STATUS } from 'upgrade_types'; * * const site = 'dashboard'; * const target = 'experimental button'; * const condition = 'variant_x'; // send null if no condition / no experiment is running / error * const status: MARKED_DECISION_POINT_STATUS = MARKED_DECISION_POINT_STATUS.CONDITION_FAILED_TO_APPLY * const clientError = 'variant not recognized'; //optional * * const markResponse = await upgradeClient.markDecisionPoint(site, target, condition, MARKED_DECISION_POINT_STATUS.CONDITION_APPLIED); * ``` * * Note*: mark can also be called via `Assignment.markDecisionPoint()` when returning an assignment from `getDecisionPointAssignment()`: * ```ts * const assignment: Assignment[] = await upgradeClient.getDecisionPointAssignment(site, target); * const markResponse = await assignment.markDecisionPoint(MARKED_DECISION_POINT_STATUS.CONDITION_APPLIED); * ``` */ markDecisionPoint(site: string, target: string, condition: string | null, status: MARKED_DECISION_POINT_STATUS, uniquifier?: string, clientError?: string): Promise; /** * @deprecated * Please use "markDecisionPoint" instead. This is just a name change, the functionality is the same, but could be removed in future. * * Will record ("mark") that a user has "seen" a condition at the given decision point (site + target). * * NOTE: This method may be deprecated in favor of Assignment.markDecisionPoint() in a future release. * * Marking the decision point will record the user's condition assignment, regardless of whether the user is enrolled in an experiment. * * @param site * @param target * @param condition `condition` is the string identifier that the user was assigned to. If none is provided, the condition will be default (null) * * @param status `status` signifies a client application's note on what it did in the code with condition assignment that Upgrade provided. * Status can be one of the following: * * ```ts * export enum MARKED_DECISION_POINT_STATUS { * CONDITION_APPLIED = 'condition applied', * CONDITION_FAILED_TO_APPLY = 'condition not applied', * NO_CONDITION_ASSIGNED = 'no condition assigned', * } * ``` * * @param uniquifier A `uniquifier` unique string can be sent along to help tie a user's logged metrics to a specific marked condition. * This identifier will also need to be sent when calling `upgradeClient.log()` * This is required for 'within-subjects' experiments. * * @param clientError The client can also send along an additional `clientError` string to log context as to why a condition was not applied. * * @example * ```ts * import { MARKED_DECISION_POINT_STATUS } from 'upgrade_types'; * * const site = 'dashboard'; * const target = 'experimental button'; * const condition = 'variant_x'; // send null if no condition / no experiment is running / error * const status: MARKED_DECISION_POINT_STATUS = MARKED_DECISION_POINT_STATUS.CONDITION_FAILED_TO_APPLY * const clientError = 'variant not recognized'; //optional * * const markResponse = await upgradeClient.markExperimentPoint(site, target, condition, MARKED_DECISION_POINT_STATUS.CONDITION_APPLIED); * ``` * * Note*: mark can also be called via `Assignment.markDecisionPoint()` when returning an assignment from `getDecisionPointAssignment()`: * ```ts * const assignment: Assignment[] = await upgradeClient.getDecisionPointAssignment(site, target); * const markResponse = await assignment.markDecisionPoint(MARKED_DECISION_POINT_STATUS.CONDITION_APPLIED); * ``` */ markExperimentPoint: (site: string, target: string, condition: string | null, status: MARKED_DECISION_POINT_STATUS, uniquifier?: string, clientError?: string) => Promise; /** * Fetches flags for the user given a context and stores them in the data service. * @param options.ignoreCache If true, it will ignore the cached feature flags and fetch fresh data from the API. * * @example * ```typescript * const featureFlags = await upgradeClient.getAllFeatureFlags(); * console.log(featureFlags); // ['feature1', 'feature2', 'feature3'] * ``` * * NOTE: See `#featureFlagUserGroupsForSession` option explanation in the constructor of UpgradeClient * to see configurations that may affect the responses to this method */ getAllFeatureFlags(options?: { ignoreCache: boolean; }): Promise; /** * Checks if a specific feature flag is enabled for the user. * Note: will await a promise if feature flags have not been fetched yet! * * @example * ```typescript * const isFeatureEnabled = await upgradeClient.hasFeatureFlag('feature1'); * console.log(isFeatureEnabled); // true or false * ``` * * NOTE: See `#featureFlagUserGroupsForSession` option explanation in the constructor of UpgradeClient * to see configurations that may affect the responses to this method */ hasFeatureFlag(key: string): Promise; /** * Will report user outcome metrics to Upgrade. * Please see https://upgrade-platform.gitbook.io/docs/developer-guide/reference/metrics for more information. * * @example * ```ts * const metrics: ILogInput[] = [ * { * userId, * timestamp: '2022-03-03T19:49:00.496', * metrics: { * attributes: { * totalTimeSeconds: 41834, * totalMasteryWorkspacesCompleted: 15, * totalConceptBuildersCompleted: 17, * totalMasteryWorkspacesGraduated: 15, * totalSessions: 50, * totalProblemsCompleted: 249, * }, * groupedMetrics: [ * { * groupClass: 'conceptBuilderWorkspace', * groupKey: 'graphs_of_functions', * groupUniquifier: '2022-02-03T19:48:53.861Z', * attributes: { * timeSeconds: 488, * hintCount: 2, * errorCount: 15, * completionCount: 1, * workspaceCompletionStatus: 'GRADUATED', * problemsCompleted: 4, * }, * }, * ], * }, * ]; * * const logResponse: ILog[] = await upgradeClient.metrics(metrics); * ``` */ log(value: ILogInput[]): Promise; /** * Will report Caliper user outcome metrics to Upgrade, same as log() but with Caliper envelope. * * @example * ```ts * const logRequest: CaliperEnvelope = { sensor: 'test', sendTime: 'test', dataVersion: 'test', data: [], }; * * * const logCaliperResponse: ILog[] = await upgradeClient.logCaliper(logRequest); * * ``` */ logCaliper(value: CaliperEnvelope): Promise; /** * Will set an array of alternate user ids for the user. * * @example * ```ts * const aliases: string[] = ['alias1', 'alias2']; * * const setAltUserIdsResponse: IExperimentUserAliases[] = await upgradeClient.setAltUserIds(aliases); * ``` */ setAltUserIds(altUserIds: string[]): Promise; /** * Sends a binary reward signal for an adaptive experiment (Mooclet). * * This method allows sending reward feedback (SUCCESS or FAILURE) for adaptive experiments. * The reward is used by the adaptive algorithm to update its learning model and improve future assignments. * * **Reward Values:** * You can pass reward values in two ways: * - As string literals: 'SUCCESS' or 'FAILURE' * - Using the enum: UpgradeClient.BINARY_REWARD_VALUE.SUCCESS or UpgradeClient.BINARY_REWARD_VALUE.FAILURE * * **Lookup Methods:** * * The method supports two ways to identify the experiment: * * 1. **Direct Lookup** - Provide the `experimentId` directly * 2. **Decision Point Lookup** - Provide `context` and `decisionPoint` (site and target) to look up the experiment * * At least one of these methods must be provided. * * @example * ```ts * // Example 1: Using string literals with experimentId * const response = await upgradeClient.sendReward({ * rewardValue: 'SUCCESS', * experimentId: 'exp_adaptive_123' * }); * ``` * * @example * ```ts * // Example 2: Using the enum with experimentId * const response = await upgradeClient.sendReward({ * rewardValue: UpgradeClient.BINARY_REWARD_VALUE.SUCCESS, * experimentId: 'exp_adaptive_123' * }); * ``` * * @example * ```ts * // Example 3: Using decision point lookup with string literals * const response = await upgradeClient.sendReward({ * rewardValue: 'FAILURE', * context: 'learning-module', * decisionPoint: { * site: 'math-course', * target: 'problem-set-1' * } * }); * ``` * * @example * ```ts * // Example 4: Using decision point lookup with enum * const response = await upgradeClient.sendReward({ * rewardValue: UpgradeClient.BINARY_REWARD_VALUE.FAILURE, * context: 'learning-module', * decisionPoint: { * site: 'math-course', * target: 'problem-set-1' * } * }); * ``` */ sendReward(params: { rewardValue: 'SUCCESS' | 'FAILURE'; experimentId?: string; context?: string; decisionPoint?: { site: string; target: string; }; }): Promise; }