diff --git a/clients/js/src/index.ts b/clients/js/src/index.ts index 7b1c1ab1..8907e74c 100644 --- a/clients/js/src/index.ts +++ b/clients/js/src/index.ts @@ -1,7 +1,7 @@ import * as jsonLogic from 'json-logic-js'; import { deepMerge } from './utils/deepMerge'; import { compareSemanticIsGreater } from './utils/operations' -import { IObject, Dimension } from './types' +import { IObject, Dimension, Experiments, Variant, VariantType, Variants } from './types' type DataFromCacApi = { contexts: Array; @@ -39,3 +39,55 @@ export class CacReader { return deepMerge(targetConfig, ...requiredOverrides); } } + +export class ExperimentReader { + experiments: Experiments; + + constructor(experiments: Experiments) { + this.experiments = experiments; + } + + public getApplicableVariant(data: IObject, toss: number): Array { + if (!Number.isInteger(toss)) { + throw new Error("Invalid toss, valid range: -1 to 100"); + } + const experiments = this.getSatisfiedExperiments(data); + const variants = []; + for (const exp of experiments) { + const v = this.decideVariant(exp.traffic_percentage, exp.variants, toss); + if (v) { + variants.push(v.id) + } + } + return variants; + }; + + getSatisfiedExperiments(data: IObject): Experiments { + return this.experiments.filter(exp => jsonLogic.apply(exp.context, data)); + }; + + // decide which variant to return among all applicable experiments + decideVariant( + traffic: number, + applicable_variants: Variants, + toss: number, + ): Variant | undefined { + if (!Number.isInteger(traffic) || !Number.isInteger(toss)) { + return undefined; + } + if (toss < 0) { + for (const variant of applicable_variants) { + if (variant.variant_type == VariantType.EXPERIMENTAL) { + return variant; + } + } + } + const variant_count = applicable_variants.length; + const range = traffic * variant_count; + if (toss >= range) { + return undefined; + } + const index = Math.floor(toss / traffic); + return applicable_variants[index]; + } +} diff --git a/clients/js/src/types.ts b/clients/js/src/types.ts index e9a02370..9d0271ed 100644 --- a/clients/js/src/types.ts +++ b/clients/js/src/types.ts @@ -13,4 +13,34 @@ export type DimensionConfig = { export type IIsObject = { (item: any): boolean; -} \ No newline at end of file +} + +export enum VariantType { + CONTROL = "CONTROL", + EXPERIMENTAL = "EXPERIMENTAL", +} + +export type Variant = { + id: String, + overrides: Object, + variant_type: VariantType, +} + +export type Variants = Array; + +export enum ExperimentStatusType { + CREATED = "CREATED", + INPROGRESS = "INPROGRESS", + CONCLUDED = "CONCLUDED", +} + +export type Experiment = { + variants: Variants, + name: String, + id: String, + traffic_percentage: number, + context: Object, + status: ExperimentStatusType, +} + +export type Experiments = Array;