From fad235bd505064d992c059a2e00e90d0203d0a81 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 14 Mar 2025 16:38:53 -0300 Subject: [PATCH 01/13] Add impression properties support --- src/logger/messages/error.ts | 2 +- src/sdkClient/client.ts | 24 +++---- src/sdkClient/clientInputValidation.ts | 29 ++++---- src/sdkFactory/index.ts | 9 ++- src/sync/submitters/impressionsSubmitter.ts | 5 +- src/sync/submitters/types.ts | 2 + .../__tests__/impressionsTracker.spec.ts | 16 +++-- src/trackers/impressionsTracker.ts | 9 ++- src/trackers/types.ts | 2 +- src/utils/inputValidation/eventProperties.ts | 8 +++ src/utils/inputValidation/index.ts | 1 + types/splitio.d.ts | 71 ++++++++++--------- 12 files changed, 103 insertions(+), 75 deletions(-) diff --git a/src/logger/messages/error.ts b/src/logger/messages/error.ts index 2c0b0c63..123f8eee 100644 --- a/src/logger/messages/error.ts +++ b/src/logger/messages/error.ts @@ -21,7 +21,7 @@ export const codesError: [number, string][] = [ // input validation [c.ERROR_EVENT_TYPE_FORMAT, '%s: you passed "%s", event_type must adhere to the regular expression /^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/g. This means an event_type must be alphanumeric, cannot be more than 80 characters long, and can only include a dash, underscore, period, or colon as separators of alphanumeric characters.'], [c.ERROR_NOT_PLAIN_OBJECT, '%s: %s must be a plain object.'], - [c.ERROR_SIZE_EXCEEDED, '%s: the maximum size allowed for the properties is 32768 bytes, which was exceeded. Event not queued.'], + [c.ERROR_SIZE_EXCEEDED, '%s: the maximum size allowed for the properties is 32768 bytes, which was exceeded.'], [c.ERROR_NOT_FINITE, '%s: value must be a finite number.'], [c.ERROR_NULL, '%s: you passed a null or undefined %s. It must be a non-empty string.'], [c.ERROR_TOO_LONG, '%s: %s too long. It must have 250 characters or less.'], diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index 1139a272..e61a393e 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -56,7 +56,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl return getTreatment(key, featureFlagName, attributes, true, GET_TREATMENT_WITH_CONFIG); } - function getTreatments(key: SplitIO.SplitKey, featureFlagNames: string[], attributes: SplitIO.Attributes | undefined, withConfig = false, methodName = GET_TREATMENTS) { + function getTreatments(key: SplitIO.SplitKey, featureFlagNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENTS) { const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS); const wrapUp = (evaluationResults: Record) => { @@ -65,7 +65,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl Object.keys(evaluationResults).forEach(featureFlagName => { treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue); }); - impressionsTracker.track(queue, attributes); + impressionsTracker.track(queue, attributes, options); stopTelemetryTracker(queue[0] && queue[0].imp.label); return treatments; @@ -80,11 +80,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations); } - function getTreatmentsWithConfig(key: SplitIO.SplitKey, featureFlagNames: string[], attributes: SplitIO.Attributes | undefined) { - return getTreatments(key, featureFlagNames, attributes, true, GET_TREATMENTS_WITH_CONFIG); + function getTreatmentsWithConfig(key: SplitIO.SplitKey, featureFlagNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) { + return getTreatments(key, featureFlagNames, attributes, options, true, GET_TREATMENTS_WITH_CONFIG); } - function getTreatmentsByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes: SplitIO.Attributes | undefined, withConfig = false, method: Method = TREATMENTS_BY_FLAGSETS, methodName = GET_TREATMENTS_BY_FLAG_SETS) { + function getTreatmentsByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes: SplitIO.Attributes | undefined, options: SplitIO.EvaluationOptions | undefined, withConfig = false, method: Method = TREATMENTS_BY_FLAGSETS, methodName = GET_TREATMENTS_BY_FLAG_SETS) { const stopTelemetryTracker = telemetryTracker.trackEval(method); const wrapUp = (evaluationResults: Record) => { @@ -94,7 +94,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl Object.keys(evaluations).forEach(featureFlagName => { treatments[featureFlagName] = processEvaluation(evaluations[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue); }); - impressionsTracker.track(queue, attributes); + impressionsTracker.track(queue, attributes, options); stopTelemetryTracker(queue[0] && queue[0].imp.label); return treatments; @@ -109,16 +109,16 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations); } - function getTreatmentsWithConfigByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes: SplitIO.Attributes | undefined) { - return getTreatmentsByFlagSets(key, flagSetNames, attributes, true, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); + function getTreatmentsWithConfigByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) { + return getTreatmentsByFlagSets(key, flagSetNames, attributes, options, true, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS); } - function getTreatmentsByFlagSet(key: SplitIO.SplitKey, flagSetName: string, attributes: SplitIO.Attributes | undefined) { - return getTreatmentsByFlagSets(key, [flagSetName], attributes, false, TREATMENTS_BY_FLAGSET, GET_TREATMENTS_BY_FLAG_SET); + function getTreatmentsByFlagSet(key: SplitIO.SplitKey, flagSetName: string, attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) { + return getTreatmentsByFlagSets(key, [flagSetName], attributes, options, false, TREATMENTS_BY_FLAGSET, GET_TREATMENTS_BY_FLAG_SET); } - function getTreatmentsWithConfigByFlagSet(key: SplitIO.SplitKey, flagSetName: string, attributes: SplitIO.Attributes | undefined) { - return getTreatmentsByFlagSets(key, [flagSetName], attributes, true, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET); + function getTreatmentsWithConfigByFlagSet(key: SplitIO.SplitKey, flagSetName: string, attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) { + return getTreatmentsByFlagSets(key, [flagSetName], attributes, options, true, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET); } // Internal function diff --git a/src/sdkClient/clientInputValidation.ts b/src/sdkClient/clientInputValidation.ts index e7fb9db1..38207656 100644 --- a/src/sdkClient/clientInputValidation.ts +++ b/src/sdkClient/clientInputValidation.ts @@ -9,7 +9,8 @@ import { validateSplits, validateTrafficType, validateIfNotDestroyed, - validateIfOperational + validateIfOperational, + validateEvaluationOptions } from '../utils/inputValidation'; import { startsWith } from '../utils/lang'; import { CONTROL, CONTROL_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENT_WITH_CONFIG, TRACK_FN_LABEL } from '../utils/constants'; @@ -32,7 +33,7 @@ export function clientInputValidationDecorator -1 ? @@ -43,6 +44,7 @@ export function clientInputValidationDecorator res[split] = CONTROL); @@ -94,7 +97,7 @@ export function clientInputValidationDecorator false) }; +const fakeDebugStrategy = { + process: jest.fn(() => false) +}; + /* Tests */ describe('Impressions Tracker', () => { @@ -53,7 +57,7 @@ describe('Impressions Tracker', () => { const strategy = strategyDebugFactory(impressionObserverCSFactory()); test('Should be able to track impressions (in DEBUG mode without Previous Time).', () => { - const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit); + const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategy, fakeWhenInit); const imp1 = { feature: '10', @@ -73,7 +77,7 @@ describe('Impressions Tracker', () => { }); test('Tracked impressions should be sent to impression listener and integration manager when we invoke .track()', (done) => { - const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit, fakeIntegrationsManager); + const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategy, fakeWhenInit, fakeIntegrationsManager); const fakeImpression = { feature: 'impression' @@ -147,8 +151,8 @@ describe('Impressions Tracker', () => { impression3.time = 1234567891; const trackers = [ - impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverSSFactory()), fakeWhenInit, undefined), - impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverCSFactory()), fakeWhenInit, undefined) + impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategyDebugFactory(impressionObserverSSFactory()), fakeWhenInit, undefined), + impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategyDebugFactory(impressionObserverCSFactory()), fakeWhenInit, undefined) ]; expect(fakeImpressionsCache.track).not.toBeCalled(); // storage method should not be called until impressions are tracked. @@ -175,7 +179,7 @@ describe('Impressions Tracker', () => { impression3.time = Date.now(); const impressionCountsCache = new ImpressionCountsCacheInMemory(); - const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), fakeWhenInit, undefined, fakeTelemetryCache as any); + const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), fakeWhenInit, undefined, fakeTelemetryCache as any); expect(fakeImpressionsCache.track).not.toBeCalled(); // cache method should not be called by just creating a tracker @@ -198,7 +202,7 @@ describe('Impressions Tracker', () => { test('Should track or not impressions depending on user consent status', () => { const settings = { ...fullSettings }; - const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit); + const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategy, fakeWhenInit); tracker.track([{ imp: impression }]); expect(fakeImpressionsCache.track).toBeCalledTimes(1); // impression should be tracked if userConsent is undefined diff --git a/src/trackers/impressionsTracker.ts b/src/trackers/impressionsTracker.ts index 0d64a8ed..acf5fa3b 100644 --- a/src/trackers/impressionsTracker.ts +++ b/src/trackers/impressionsTracker.ts @@ -14,7 +14,8 @@ export function impressionsTrackerFactory( settings: ISettings, impressionsCache: IImpressionsCacheBase, noneStrategy: IStrategy, - strategy: IStrategy, + debugStrategy: IStrategy, + defaultStrategy: IStrategy, whenInit: (cb: () => void) => void, integrationsManager?: IImpressionsHandler, telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync, @@ -23,13 +24,15 @@ export function impressionsTrackerFactory( const { log, impressionListener, runtime: { ip, hostname }, version } = settings; return { - track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes) { + track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) { if (settings.userConsent === CONSENT_DECLINED) return; const impressionsToStore = impressions.filter(({ imp, disabled }) => { return disabled ? noneStrategy.process(imp) : - strategy.process(imp); + options && options.properties ? + (imp.properties = options.properties) && debugStrategy.process(imp) : + defaultStrategy.process(imp); }); const impressionsLength = impressions.length; diff --git a/src/trackers/types.ts b/src/trackers/types.ts index a0dd2dd4..7ac928fb 100644 --- a/src/trackers/types.ts +++ b/src/trackers/types.ts @@ -29,7 +29,7 @@ export type ImpressionDecorated = { }; export interface IImpressionsTracker { - track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes): void + track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions): void } /** Telemetry tracker */ diff --git a/src/utils/inputValidation/eventProperties.ts b/src/utils/inputValidation/eventProperties.ts index 310946cc..4a0f4e5c 100644 --- a/src/utils/inputValidation/eventProperties.ts +++ b/src/utils/inputValidation/eventProperties.ts @@ -66,3 +66,11 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth return output; } + +export function validateEvaluationOptions(log: ILogger, maybeOptions: any, method: string): SplitIO.EvaluationOptions | undefined { + if (isObject(maybeOptions)) { + const properties = validateEventProperties(log, maybeOptions.properties, method).properties; + return properties ? { properties } : undefined; + } + return undefined; +} diff --git a/src/utils/inputValidation/index.ts b/src/utils/inputValidation/index.ts index 93b09ab7..96cf4be6 100644 --- a/src/utils/inputValidation/index.ts +++ b/src/utils/inputValidation/index.ts @@ -11,3 +11,4 @@ export { validateIfNotDestroyed, validateIfOperational } from './isOperational'; export { validateSplitExistence } from './splitExistence'; export { validateTrafficTypeExistence } from './trafficTypeExistence'; export { validatePreloadedData } from './preloadedData'; +export { validateEvaluationOptions } from './eventProperties'; diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 0cab4b66..81ca4d99 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -778,6 +778,12 @@ declare namespace SplitIO { type Properties = { [propertyName: string]: string | number | boolean | null; }; + /** + * Evaluation options object for getTreatment methods. + */ + type EvaluationOptions = { + properties?: Properties; + } /** * The SplitKey object format. */ @@ -811,6 +817,7 @@ declare namespace SplitIO { label: string; changeNumber: number; pt?: number; + properties?: Properties; } /** * Object with information about an impression. It contains the generated impression DTO as well as @@ -1516,7 +1523,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The treatment string. */ - getTreatment(key: SplitKey, featureFlagName: string, attributes?: Attributes): Treatment; + getTreatment(key: SplitKey, featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): Treatment; /** * Returns a TreatmentWithConfig value, which is an object with both treatment and config string for the given feature. * @@ -1526,7 +1533,7 @@ declare namespace SplitIO { * @returns The TreatmentWithConfig, the object containing the treatment string and the * configuration stringified JSON (or null if there was no config for that treatment). */ - getTreatmentWithConfig(key: SplitKey, featureFlagName: string, attributes?: Attributes): TreatmentWithConfig; + getTreatmentWithConfig(key: SplitKey, featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): TreatmentWithConfig; /** * Returns a Treatments value, which is an object map with the treatments for the given features. * @@ -1535,7 +1542,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The treatments object map. */ - getTreatments(key: SplitKey, featureFlagNames: string[], attributes?: Attributes): Treatments; + getTreatments(key: SplitKey, featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): Treatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the given features. * @@ -1544,7 +1551,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the TreatmentWithConfig objects */ - getTreatmentsWithConfig(key: SplitKey, featureFlagNames: string[], attributes?: Attributes): TreatmentsWithConfig; + getTreatmentsWithConfig(key: SplitKey, featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; /** * Returns a Treatments value, which is an object map with the treatments for the feature flags related to the given flag set. * @@ -1553,7 +1560,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the Treatment objects */ - getTreatmentsByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes): Treatments; + getTreatmentsByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes, options?: EvaluationOptions): Treatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the feature flags related to the given flag set. * @@ -1562,7 +1569,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the TreatmentWithConfig objects */ - getTreatmentsWithConfigByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes): TreatmentsWithConfig; + getTreatmentsWithConfigByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; /** * Returns a Returns a Treatments value, which is an object with both treatment and config string for to the feature flags related to the given flag sets. * @@ -1571,7 +1578,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the Treatment objects */ - getTreatmentsByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes): Treatments; + getTreatmentsByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): Treatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the feature flags related to the given flag sets. * @@ -1580,7 +1587,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the TreatmentWithConfig objects */ - getTreatmentsWithConfigByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes): TreatmentsWithConfig; + getTreatmentsWithConfigByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; /** * Tracks an event to be fed to the results product on Split user interface. * @@ -1607,7 +1614,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns Treatment promise that resolves to the treatment string. */ - getTreatment(key: SplitKey, featureFlagName: string, attributes?: Attributes): AsyncTreatment; + getTreatment(key: SplitKey, featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatment; /** * Returns a TreatmentWithConfig value, which will be (or eventually be) an object with both treatment and config string for the given feature. * @@ -1616,7 +1623,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns TreatmentWithConfig promise that resolves to the TreatmentWithConfig object. */ - getTreatmentWithConfig(key: SplitKey, featureFlagName: string, attributes?: Attributes): AsyncTreatmentWithConfig; + getTreatmentWithConfig(key: SplitKey, featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentWithConfig; /** * Returns a Treatments value, which will be (or eventually be) an object map with the treatments for the given features. * @@ -1625,7 +1632,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns Treatments promise that resolves to the treatments object map. */ - getTreatments(key: SplitKey, featureFlagNames: string[], attributes?: Attributes): AsyncTreatments; + getTreatments(key: SplitKey, featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; /** * Returns a TreatmentsWithConfig value, which will be (or eventually be) an object map with the TreatmentWithConfig (an object with both treatment and config string) for the given features. * @@ -1634,7 +1641,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns TreatmentsWithConfig promise that resolves to the map of TreatmentsWithConfig objects. */ - getTreatmentsWithConfig(key: SplitKey, featureFlagNames: string[], attributes?: Attributes): AsyncTreatmentsWithConfig; + getTreatmentsWithConfig(key: SplitKey, featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; /** * Returns a Treatments value, which is an object map with the treatments for the feature flags related to the given flag set. * @@ -1643,7 +1650,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns Treatments promise that resolves to the treatments object map. */ - getTreatmentsByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes): AsyncTreatments; + getTreatmentsByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the feature flags related to the given flag set. * @@ -1652,7 +1659,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns TreatmentsWithConfig promise that resolves to the map of TreatmentsWithConfig objects. */ - getTreatmentsWithConfigByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes): AsyncTreatmentsWithConfig; + getTreatmentsWithConfigByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; /** * Returns a Returns a Treatments value, which is an object with both treatment and config string for to the feature flags related to the given flag sets. * @@ -1661,7 +1668,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns Treatments promise that resolves to the treatments object map. */ - getTreatmentsByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes): AsyncTreatments; + getTreatmentsByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the feature flags related to the given flag sets. * @@ -1670,7 +1677,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns TreatmentsWithConfig promise that resolves to the map of TreatmentsWithConfig objects. */ - getTreatmentsWithConfigByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes): AsyncTreatmentsWithConfig; + getTreatmentsWithConfigByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; /** * Tracks an event to be fed to the results product on Split user interface, and returns a promise to signal when the event was successfully queued (or not). * @@ -1737,7 +1744,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The treatment string. */ - getTreatment(featureFlagName: string, attributes?: Attributes): Treatment; + getTreatment(featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): Treatment; /** * Returns a TreatmentWithConfig value, which is an object with both treatment and config string for the given feature. * @@ -1745,7 +1752,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map containing the treatment and the configuration stringified JSON (or null if there was no config for that treatment). */ - getTreatmentWithConfig(featureFlagName: string, attributes?: Attributes): TreatmentWithConfig; + getTreatmentWithConfig(featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): TreatmentWithConfig; /** * Returns a Treatments value, which is an object map with the treatments for the given features. * @@ -1753,7 +1760,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The treatments object map. */ - getTreatments(featureFlagNames: string[], attributes?: Attributes): Treatments; + getTreatments(featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): Treatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the given features. * @@ -1761,7 +1768,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the TreatmentWithConfig objects */ - getTreatmentsWithConfig(featureFlagNames: string[], attributes?: Attributes): TreatmentsWithConfig; + getTreatmentsWithConfig(featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; /** * Returns a Treatments value, which is an object map with the treatments for the feature flags related to the given flag set. * @@ -1769,7 +1776,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the Treatments objects */ - getTreatmentsByFlagSet(flagSet: string, attributes?: Attributes): Treatments; + getTreatmentsByFlagSet(flagSet: string, attributes?: Attributes, options?: EvaluationOptions): Treatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the feature flags related to the given flag set. * @@ -1777,7 +1784,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the TreatmentWithConfig objects */ - getTreatmentsWithConfigByFlagSet(flagSet: string, attributes?: Attributes): TreatmentsWithConfig; + getTreatmentsWithConfigByFlagSet(flagSet: string, attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; /** * Returns a Returns a Treatments value, which is an object with both treatment and config string for to the feature flags related to the given flag sets. * @@ -1785,7 +1792,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the Treatments objects */ - getTreatmentsByFlagSets(flagSets: string[], attributes?: Attributes): Treatments; + getTreatmentsByFlagSets(flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): Treatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the feature flags related to the given flag sets. * @@ -1793,7 +1800,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns The map with all the TreatmentWithConfig objects */ - getTreatmentsWithConfigByFlagSets(flagSets: string[], attributes?: Attributes): TreatmentsWithConfig; + getTreatmentsWithConfigByFlagSets(flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; /** * Tracks an event to be fed to the results product on Split user interface. * @@ -1816,7 +1823,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns Treatment promise that resolves to the treatment string. */ - getTreatment(featureFlagName: string, attributes?: Attributes): AsyncTreatment; + getTreatment(featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatment; /** * Returns a TreatmentWithConfig value, which will be (or eventually be) an object with both treatment and config string for the given feature. * @@ -1824,7 +1831,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns TreatmentWithConfig promise that resolves to the TreatmentWithConfig object. */ - getTreatmentWithConfig(featureFlagName: string, attributes?: Attributes): AsyncTreatmentWithConfig; + getTreatmentWithConfig(featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentWithConfig; /** * Returns a Treatments value, which will be (or eventually be) an object map with the treatments for the given features. * @@ -1832,7 +1839,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns Treatments promise that resolves to the treatments object map. */ - getTreatments(featureFlagNames: string[], attributes?: Attributes): AsyncTreatments; + getTreatments(featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; /** * Returns a TreatmentsWithConfig value, which will be (or eventually be) an object map with the TreatmentWithConfig (an object with both treatment and config string) for the given features. * @@ -1840,7 +1847,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns TreatmentsWithConfig promise that resolves to the TreatmentsWithConfig object. */ - getTreatmentsWithConfig(featureFlagNames: string[], attributes?: Attributes): AsyncTreatmentsWithConfig; + getTreatmentsWithConfig(featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; /** * Returns a Treatments value, which is an object map with the treatments for the feature flags related to the given flag set. * @@ -1848,7 +1855,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns Treatments promise that resolves to the treatments object map. */ - getTreatmentsByFlagSet(flagSet: string, attributes?: Attributes): AsyncTreatments; + getTreatmentsByFlagSet(flagSet: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the feature flags related to the given flag set. * @@ -1856,7 +1863,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns TreatmentsWithConfig promise that resolves to the TreatmentsWithConfig object. */ - getTreatmentsWithConfigByFlagSet(flagSet: string, attributes?: Attributes): AsyncTreatmentsWithConfig; + getTreatmentsWithConfigByFlagSet(flagSet: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; /** * Returns a Returns a Treatments value, which is an object with both treatment and config string for to the feature flags related to the given flag sets. * @@ -1864,7 +1871,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns Treatments promise that resolves to the treatments object map. */ - getTreatmentsByFlagSets(flagSets: string[], attributes?: Attributes): AsyncTreatments; + getTreatmentsByFlagSets(flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; /** * Returns a TreatmentsWithConfig value, which is an object map with the TreatmentWithConfig (an object with both treatment and config string) for the feature flags related to the given flag sets. * @@ -1872,7 +1879,7 @@ declare namespace SplitIO { * @param attributes - An object of type Attributes defining the attributes for the given key. * @returns TreatmentsWithConfig promise that resolves to the TreatmentsWithConfig object. */ - getTreatmentsWithConfigByFlagSets(flagSets: string[], attributes?: Attributes): AsyncTreatmentsWithConfig; + getTreatmentsWithConfigByFlagSets(flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; /** * Tracks an event to be fed to the results product on Split user interface, and returns a promise to signal when the event was successfully queued (or not). * From a3ab3068264f5f8b4838575706ec139f59d7083a Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 18 Mar 2025 23:00:51 -0300 Subject: [PATCH 02/13] Debug strategy for impressions with properties --- src/sdkFactory/index.ts | 9 +++++---- .../__tests__/impressionsTracker.spec.ts | 16 ++++++---------- src/trackers/impressionsTracker.ts | 9 ++++----- src/trackers/strategy/strategyOptimized.ts | 3 +++ 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/sdkFactory/index.ts b/src/sdkFactory/index.ts index ad1e8432..b342a6e0 100644 --- a/src/sdkFactory/index.ts +++ b/src/sdkFactory/index.ts @@ -62,12 +62,13 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA const uniqueKeysTracker = uniqueKeysTrackerFactory(log, storage.uniqueKeys, filterAdapterFactory && filterAdapterFactory()); const noneStrategy = strategyNoneFactory(storage.impressionCounts, uniqueKeysTracker); - const debugStrategy = strategyDebugFactory(observer); - const defaultStrategy = impressionsMode === OPTIMIZED ? + const strategy = impressionsMode === OPTIMIZED ? strategyOptimizedFactory(observer, storage.impressionCounts) : - impressionsMode === DEBUG ? debugStrategy : noneStrategy; + impressionsMode === DEBUG ? + strategyDebugFactory(observer) : + noneStrategy; - const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, debugStrategy, defaultStrategy, whenInit, integrationsManager, storage.telemetry); + const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, whenInit, integrationsManager, storage.telemetry); const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry); // splitApi is used by SyncManager and Browser signal listener diff --git a/src/trackers/__tests__/impressionsTracker.spec.ts b/src/trackers/__tests__/impressionsTracker.spec.ts index 52376b9f..1db3875f 100644 --- a/src/trackers/__tests__/impressionsTracker.spec.ts +++ b/src/trackers/__tests__/impressionsTracker.spec.ts @@ -40,10 +40,6 @@ const fakeNoneStrategy = { process: jest.fn(() => false) }; -const fakeDebugStrategy = { - process: jest.fn(() => false) -}; - /* Tests */ describe('Impressions Tracker', () => { @@ -57,7 +53,7 @@ describe('Impressions Tracker', () => { const strategy = strategyDebugFactory(impressionObserverCSFactory()); test('Should be able to track impressions (in DEBUG mode without Previous Time).', () => { - const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategy, fakeWhenInit); + const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit); const imp1 = { feature: '10', @@ -77,7 +73,7 @@ describe('Impressions Tracker', () => { }); test('Tracked impressions should be sent to impression listener and integration manager when we invoke .track()', (done) => { - const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategy, fakeWhenInit, fakeIntegrationsManager); + const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit, fakeIntegrationsManager); const fakeImpression = { feature: 'impression' @@ -151,8 +147,8 @@ describe('Impressions Tracker', () => { impression3.time = 1234567891; const trackers = [ - impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategyDebugFactory(impressionObserverSSFactory()), fakeWhenInit, undefined), - impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategyDebugFactory(impressionObserverCSFactory()), fakeWhenInit, undefined) + impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverSSFactory()), fakeWhenInit, undefined), + impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverCSFactory()), fakeWhenInit, undefined) ]; expect(fakeImpressionsCache.track).not.toBeCalled(); // storage method should not be called until impressions are tracked. @@ -179,7 +175,7 @@ describe('Impressions Tracker', () => { impression3.time = Date.now(); const impressionCountsCache = new ImpressionCountsCacheInMemory(); - const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), fakeWhenInit, undefined, fakeTelemetryCache as any); + const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), fakeWhenInit, undefined, fakeTelemetryCache as any); expect(fakeImpressionsCache.track).not.toBeCalled(); // cache method should not be called by just creating a tracker @@ -202,7 +198,7 @@ describe('Impressions Tracker', () => { test('Should track or not impressions depending on user consent status', () => { const settings = { ...fullSettings }; - const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategy, fakeWhenInit); + const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit); tracker.track([{ imp: impression }]); expect(fakeImpressionsCache.track).toBeCalledTimes(1); // impression should be tracked if userConsent is undefined diff --git a/src/trackers/impressionsTracker.ts b/src/trackers/impressionsTracker.ts index acf5fa3b..2529633a 100644 --- a/src/trackers/impressionsTracker.ts +++ b/src/trackers/impressionsTracker.ts @@ -14,8 +14,7 @@ export function impressionsTrackerFactory( settings: ISettings, impressionsCache: IImpressionsCacheBase, noneStrategy: IStrategy, - debugStrategy: IStrategy, - defaultStrategy: IStrategy, + strategy: IStrategy, whenInit: (cb: () => void) => void, integrationsManager?: IImpressionsHandler, telemetryCache?: ITelemetryCacheSync | ITelemetryCacheAsync, @@ -28,11 +27,11 @@ export function impressionsTrackerFactory( if (settings.userConsent === CONSENT_DECLINED) return; const impressionsToStore = impressions.filter(({ imp, disabled }) => { + if (options && options.properties) imp.properties = options.properties; + return disabled ? noneStrategy.process(imp) : - options && options.properties ? - (imp.properties = options.properties) && debugStrategy.process(imp) : - defaultStrategy.process(imp); + strategy.process(imp); }); const impressionsLength = impressions.length; diff --git a/src/trackers/strategy/strategyOptimized.ts b/src/trackers/strategy/strategyOptimized.ts index 9a9cf883..8220de7e 100644 --- a/src/trackers/strategy/strategyOptimized.ts +++ b/src/trackers/strategy/strategyOptimized.ts @@ -20,6 +20,9 @@ export function strategyOptimizedFactory( process(impression: SplitIO.ImpressionDTO) { impression.pt = impressionsObserver.testAndSet(impression); + // DEBUG mode for impressions with properties + if (impression.properties) return true; + const now = Date.now(); // Increments impression counter per featureName From a976968dda4f436d416ae8045189ba8747ba2939 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 19 Mar 2025 17:52:50 -0300 Subject: [PATCH 03/13] Add options to client methods --- src/logger/messages/warn.ts | 2 +- src/sdkClient/client.ts | 22 +++++----- src/sdkClient/clientAttributesDecoration.ts | 41 +++++++++--------- src/sdkClient/clientInputValidation.ts | 44 ++++++++++---------- src/trackers/impressionsTracker.ts | 4 +- src/trackers/strategy/strategyOptimized.ts | 6 +-- src/trackers/types.ts | 2 +- src/utils/inputValidation/eventProperties.ts | 2 + types/splitio.d.ts | 5 ++- 9 files changed, 66 insertions(+), 62 deletions(-) diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index 52487f95..568771a8 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -18,7 +18,7 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.CLIENT_NO_LISTENER, 'No listeners for SDK Readiness detected. Incorrect control treatments could have been logged if you called getTreatment/s while the SDK was not yet ready.'], // input validation [c.WARN_SETTING_NULL, '%s: Property "%s" is of invalid type. Setting value to null.'], - [c.WARN_TRIMMING_PROPERTIES, '%s: Event has more than 300 properties. Some of them will be trimmed when processed.'], + [c.WARN_TRIMMING_PROPERTIES, '%s: more than 300 properties were provided. Some of them will be trimmed when processed.'], [c.WARN_CONVERTING, '%s: %s "%s" is not of type string, converting.'], [c.WARN_TRIMMING, '%s: %s "%s" has extra whitespace, trimming.'], [c.WARN_NOT_EXISTENT_SPLIT, '%s: feature flag "%s" does not exist in this environment. Please double check what feature flags exist in the Split user interface.'], diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index e61a393e..ab3b39e6 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -31,12 +31,12 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const { log, mode } = settings; const isAsync = isConsumerMode(mode); - function getTreatment(key: SplitIO.SplitKey, featureFlagName: string, attributes: SplitIO.Attributes | undefined, withConfig = false, methodName = GET_TREATMENT) { + function getTreatment(key: SplitIO.SplitKey, featureFlagName: string, attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENT) { const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT); const wrapUp = (evaluationResult: IEvaluationResult) => { const queue: ImpressionDecorated[] = []; - const treatment = processEvaluation(evaluationResult, featureFlagName, key, attributes, withConfig, methodName, queue); + const treatment = processEvaluation(evaluationResult, featureFlagName, key, options, withConfig, methodName, queue); impressionsTracker.track(queue, attributes); stopTelemetryTracker(queue[0] && queue[0].imp.label); @@ -52,8 +52,8 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl return thenable(evaluation) ? evaluation.then((res) => wrapUp(res)) : wrapUp(evaluation); } - function getTreatmentWithConfig(key: SplitIO.SplitKey, featureFlagName: string, attributes: SplitIO.Attributes | undefined) { - return getTreatment(key, featureFlagName, attributes, true, GET_TREATMENT_WITH_CONFIG); + function getTreatmentWithConfig(key: SplitIO.SplitKey, featureFlagName: string, attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) { + return getTreatment(key, featureFlagName, attributes, options, true, GET_TREATMENT_WITH_CONFIG); } function getTreatments(key: SplitIO.SplitKey, featureFlagNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENTS) { @@ -63,9 +63,9 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const queue: ImpressionDecorated[] = []; const treatments: Record = {}; Object.keys(evaluationResults).forEach(featureFlagName => { - treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue); + treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, options, withConfig, methodName, queue); }); - impressionsTracker.track(queue, attributes, options); + impressionsTracker.track(queue, attributes); stopTelemetryTracker(queue[0] && queue[0].imp.label); return treatments; @@ -84,7 +84,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl return getTreatments(key, featureFlagNames, attributes, options, true, GET_TREATMENTS_WITH_CONFIG); } - function getTreatmentsByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes: SplitIO.Attributes | undefined, options: SplitIO.EvaluationOptions | undefined, withConfig = false, method: Method = TREATMENTS_BY_FLAGSETS, methodName = GET_TREATMENTS_BY_FLAG_SETS) { + function getTreatmentsByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, method: Method = TREATMENTS_BY_FLAGSETS, methodName = GET_TREATMENTS_BY_FLAG_SETS) { const stopTelemetryTracker = telemetryTracker.trackEval(method); const wrapUp = (evaluationResults: Record) => { @@ -92,9 +92,9 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const treatments: Record = {}; const evaluations = evaluationResults; Object.keys(evaluations).forEach(featureFlagName => { - treatments[featureFlagName] = processEvaluation(evaluations[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue); + treatments[featureFlagName] = processEvaluation(evaluations[featureFlagName], featureFlagName, key, options, withConfig, methodName, queue); }); - impressionsTracker.track(queue, attributes, options); + impressionsTracker.track(queue, attributes); stopTelemetryTracker(queue[0] && queue[0].imp.label); return treatments; @@ -126,13 +126,14 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl evaluation: IEvaluationResult, featureFlagName: string, key: SplitIO.SplitKey, - attributes: SplitIO.Attributes | undefined, + options: SplitIO.EvaluationOptions | undefined, withConfig: boolean, invokingMethodName: string, queue: ImpressionDecorated[] ): SplitIO.Treatment | SplitIO.TreatmentWithConfig { const matchingKey = getMatching(key); const bucketingKey = getBucketing(key); + const properties = options && options.properties ? JSON.stringify(options.properties) : undefined; const { treatment, label, changeNumber, config = null, impressionsDisabled } = evaluation; log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]); @@ -148,6 +149,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl bucketingKey, label, changeNumber: changeNumber as number, + properties }, disabled: impressionsDisabled }); diff --git a/src/sdkClient/clientAttributesDecoration.ts b/src/sdkClient/clientAttributesDecoration.ts index cf31b5d3..388b3142 100644 --- a/src/sdkClient/clientAttributesDecoration.ts +++ b/src/sdkClient/clientAttributesDecoration.ts @@ -22,48 +22,47 @@ export function clientAttributesDecoration 0) { - return objectAssign({}, storedAttributes, maybeAttributes); - } - return maybeAttributes; + return Object.keys(storedAttributes).length > 0 ? + objectAssign({}, storedAttributes, maybeAttributes) : + maybeAttributes; } return objectAssign(client, { diff --git a/src/sdkClient/clientInputValidation.ts b/src/sdkClient/clientInputValidation.ts index 38207656..40765d41 100644 --- a/src/sdkClient/clientInputValidation.ts +++ b/src/sdkClient/clientInputValidation.ts @@ -67,27 +67,27 @@ export function clientInputValidationDecorator res[split] = CONTROL); @@ -96,11 +96,11 @@ export function clientInputValidationDecorator res[split] = objectAssign({}, CONTROL_WITH_CONFIG)); @@ -109,41 +109,41 @@ export function clientInputValidationDecorator { - if (options && options.properties) imp.properties = options.properties; - return disabled ? noneStrategy.process(imp) : strategy.process(imp); diff --git a/src/trackers/strategy/strategyOptimized.ts b/src/trackers/strategy/strategyOptimized.ts index 8220de7e..24c82ebd 100644 --- a/src/trackers/strategy/strategyOptimized.ts +++ b/src/trackers/strategy/strategyOptimized.ts @@ -18,11 +18,11 @@ export function strategyOptimizedFactory( return { process(impression: SplitIO.ImpressionDTO) { - impression.pt = impressionsObserver.testAndSet(impression); - - // DEBUG mode for impressions with properties + // DEBUG mode without previous time, for impressions with properties if (impression.properties) return true; + impression.pt = impressionsObserver.testAndSet(impression); + const now = Date.now(); // Increments impression counter per featureName diff --git a/src/trackers/types.ts b/src/trackers/types.ts index 7ac928fb..a0dd2dd4 100644 --- a/src/trackers/types.ts +++ b/src/trackers/types.ts @@ -29,7 +29,7 @@ export type ImpressionDecorated = { }; export interface IImpressionsTracker { - track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions): void + track(impressions: ImpressionDecorated[], attributes?: SplitIO.Attributes): void } /** Telemetry tracker */ diff --git a/src/utils/inputValidation/eventProperties.ts b/src/utils/inputValidation/eventProperties.ts index 4a0f4e5c..63178419 100644 --- a/src/utils/inputValidation/eventProperties.ts +++ b/src/utils/inputValidation/eventProperties.ts @@ -71,6 +71,8 @@ export function validateEvaluationOptions(log: ILogger, maybeOptions: any, metho if (isObject(maybeOptions)) { const properties = validateEventProperties(log, maybeOptions.properties, method).properties; return properties ? { properties } : undefined; + } else if (maybeOptions) { + log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'evaluation options']); } return undefined; } diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 81ca4d99..5aff3a88 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -817,7 +817,10 @@ declare namespace SplitIO { label: string; changeNumber: number; pt?: number; - properties?: Properties; + /** + * JSON stringified version of the impression properties. + */ + properties?: string; } /** * Object with information about an impression. It contains the generated impression DTO as well as From b24882b3e390a6c5b2e88567cd65b8842d92f297 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 20 Mar 2025 13:13:00 -0300 Subject: [PATCH 04/13] Unit tests --- .../__tests__/clientInputValidation.spec.ts | 44 ++++++++++++++++++- src/sdkClient/__tests__/testUtils.ts | 15 +++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/sdkClient/__tests__/clientInputValidation.spec.ts b/src/sdkClient/__tests__/clientInputValidation.spec.ts index 0664c179..25b224e6 100644 --- a/src/sdkClient/__tests__/clientInputValidation.spec.ts +++ b/src/sdkClient/__tests__/clientInputValidation.spec.ts @@ -3,13 +3,15 @@ import { clientInputValidationDecorator } from '../clientInputValidation'; // Mocks import { DebugLogger } from '../../logger/browser/DebugLogger'; +import { createClientMock } from './testUtils'; const settings: any = { log: DebugLogger(), sync: { __splitFiltersValidation: { groupedFilters: { bySet: [] } } } }; -const client: any = {}; +const EVALUATION_RESULT = 'on'; +const client: any = createClientMock(EVALUATION_RESULT); const readinessManager: any = { isReady: () => true, @@ -52,4 +54,44 @@ describe('clientInputValidationDecorator', () => { // @TODO should be 8, but there is an additional log from `getTreatmentsByFlagSet` and `getTreatmentsWithConfigByFlagSet` that should be removed expect(logSpy).toBeCalledTimes(10); }); + + test('should evaluate but log an error if the passed 4th argument (evaluation options) is invalid', () => { + expect(clientWithValidation.getTreatment('key', 'ff', undefined, 'invalid')).toBe(EVALUATION_RESULT); + expect(logSpy).toHaveBeenLastCalledWith('[ERROR] splitio => getTreatment: evaluation options must be a plain object.'); + expect(client.getTreatment).toBeCalledWith('key', 'ff', undefined, undefined); + + expect(clientWithValidation.getTreatmentWithConfig('key', 'ff', undefined, { properties: 'invalid' })).toBe(EVALUATION_RESULT); + expect(logSpy).toHaveBeenLastCalledWith('[ERROR] splitio => getTreatmentWithConfig: properties must be a plain object.'); + expect(client.getTreatmentWithConfig).toBeCalledWith('key', 'ff', undefined, undefined); + + expect(clientWithValidation.getTreatments('key', ['ff'], undefined, { properties: 'invalid' })).toBe(EVALUATION_RESULT); + expect(logSpy).toHaveBeenLastCalledWith('[ERROR] splitio => getTreatments: properties must be a plain object.'); + expect(client.getTreatments).toBeCalledWith('key', ['ff'], undefined, undefined); + + expect(clientWithValidation.getTreatmentsWithConfig('key', ['ff'], {}, { properties: true })).toBe(EVALUATION_RESULT); + expect(logSpy).toHaveBeenLastCalledWith('[ERROR] splitio => getTreatmentsWithConfig: properties must be a plain object.'); + expect(client.getTreatmentsWithConfig).toBeCalledWith('key', ['ff'], {}, undefined); + + expect(clientWithValidation.getTreatmentsByFlagSet('key', 'flagSet', undefined, { properties: 'invalid' })).toBe(EVALUATION_RESULT); + expect(logSpy).toHaveBeenLastCalledWith('[ERROR] splitio => getTreatmentsByFlagSet: properties must be a plain object.'); + expect(client.getTreatmentsByFlagSet).toBeCalledWith('key', 'flagset', undefined, undefined); + + expect(clientWithValidation.getTreatmentsWithConfigByFlagSet('key', 'flagSet', {}, { properties: 'invalid' })).toBe(EVALUATION_RESULT); + expect(logSpy).toBeCalledWith('[ERROR] splitio => getTreatmentsWithConfigByFlagSet: properties must be a plain object.'); + expect(client.getTreatmentsWithConfigByFlagSet).toBeCalledWith('key', 'flagset', {}, undefined); + + expect(clientWithValidation.getTreatmentsByFlagSets('key', ['flagSet'], undefined, { properties: 'invalid' })).toBe(EVALUATION_RESULT); + expect(logSpy).toHaveBeenLastCalledWith('[ERROR] splitio => getTreatmentsByFlagSets: properties must be a plain object.'); + expect(client.getTreatmentsByFlagSets).toBeCalledWith('key', ['flagset'], undefined, undefined); + + expect(clientWithValidation.getTreatmentsWithConfigByFlagSets('key', ['flagSet'], {}, { properties: 'invalid' })).toBe(EVALUATION_RESULT); + expect(logSpy).toHaveBeenLastCalledWith('[ERROR] splitio => getTreatmentsWithConfigByFlagSets: properties must be a plain object.'); + expect(client.getTreatmentsWithConfigByFlagSets).toBeCalledWith('key', ['flagset'], {}, undefined); + }); + + test('should sanitize the properties in the 4th argument', () => { + expect(clientWithValidation.getTreatment('key', 'ff', undefined, { properties: { toSanitize: /asd/, correct: 100 }})).toBe(EVALUATION_RESULT); + expect(logSpy).toHaveBeenLastCalledWith('[WARN] splitio => getTreatment: Property "toSanitize" is of invalid type. Setting value to null.'); + expect(client.getTreatment).toBeCalledWith('key', 'ff', undefined, { properties: { toSanitize: null, correct: 100 }}); + }); }); diff --git a/src/sdkClient/__tests__/testUtils.ts b/src/sdkClient/__tests__/testUtils.ts index 901897e3..dab0085a 100644 --- a/src/sdkClient/__tests__/testUtils.ts +++ b/src/sdkClient/__tests__/testUtils.ts @@ -8,3 +8,18 @@ export function assertClientApi(client: any, sdkStatus?: object) { expect(typeof client[method]).toBe('function'); }); } + +export function createClientMock(returnValue: any) { + + return { + getTreatment: jest.fn(()=> returnValue), + getTreatmentWithConfig: jest.fn(()=> returnValue), + getTreatments: jest.fn(()=> returnValue), + getTreatmentsWithConfig: jest.fn(()=> returnValue), + getTreatmentsByFlagSets: jest.fn(()=> returnValue), + getTreatmentsWithConfigByFlagSets: jest.fn(()=> returnValue), + getTreatmentsByFlagSet: jest.fn(()=> returnValue), + getTreatmentsWithConfigByFlagSet: jest.fn(()=> returnValue), + track: jest.fn(()=> returnValue), + }; +} From 7a7de540e38170b9f42ac67b2a8393c55cf7be77 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 20 Mar 2025 13:14:55 -0300 Subject: [PATCH 05/13] Remove unnecessary 'track' function overwrite in clientAttributesDecoration --- .../clientAttributesDecoration.spec.ts | 189 ++---------------- src/sdkClient/clientAttributesDecoration.ts | 6 - 2 files changed, 19 insertions(+), 176 deletions(-) diff --git a/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts b/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts index e007dc54..65aa8ef1 100644 --- a/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts +++ b/src/sdkClient/__tests__/clientAttributesDecoration.spec.ts @@ -33,7 +33,7 @@ const clientMock = { } }; // @ts-expect-error -const client = clientAttributesDecoration(loggerMock, clientMock); +const client: any = clientAttributesDecoration(loggerMock, clientMock); test('ATTRIBUTES DECORATION / storage', () => { @@ -91,9 +91,7 @@ describe('ATTRIBUTES DECORATION / validation', () => { test('Should return false if it is an invalid attributes map', () => { expect(client.setAttribute('', 'attributeValue')).toEqual(false); // It should be invalid if the attribute key is not a string - // @ts-expect-error expect(client.setAttribute('attributeKey1', new Date())).toEqual(false); // It should be invalid if the attribute value is not a String, Number, Boolean or Lists. - // @ts-expect-error expect(client.setAttribute('attributeKey2', { 'some': 'object' })).toEqual(false); // It should be invalid if the attribute value is not a String, Number, Boolean or Lists. expect(client.setAttribute('attributeKey3', Infinity)).toEqual(false); // It should be invalid if the attribute value is not a String, Number, Boolean or Lists. @@ -153,180 +151,31 @@ describe('ATTRIBUTES DECORATION / validation', () => { describe('ATTRIBUTES DECORATION / evaluation', () => { - test('Evaluation attributes logic and precedence / getTreatment', () => { - + test.each([ + ['getTreatment', 'split'], + ['getTreatments', ['split']], + ['getTreatmentWithConfig', 'split'], + ['getTreatmentsWithConfig', ['split']], + ['getTreatmentsByFlagSet', 'set'], + ['getTreatmentsWithConfigByFlagSet', 'set'], + ['getTreatmentsByFlagSets', ['set']], + ['getTreatmentsWithConfigByFlagSets', ['set']] + ])('Evaluation attributes logic and precedence / %s', (method, param) => { // If the same attribute is “cached” and provided on the function, the value received on the function call takes precedence. - expect(client.getTreatment('key', 'split')).toEqual(undefined); // Nothing changes if no attributes were provided using the new api - expect(client.getTreatment('key', 'split', { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Nothing changes if no attributes were provided using the new api + expect(client[method]('key', param)).toEqual(undefined); // Nothing changes if no attributes were provided using the new api + expect(client[method]('key', param, { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Nothing changes if no attributes were provided using the new api expect(client.getAttributes()).toEqual({}); // Attributes in memory storage must be empty client.setAttribute('func_attr_bool', false); expect(client.getAttributes()).toEqual({ 'func_attr_bool': false }); // In memory attribute storage must have the unique stored attribute - expect(client.getTreatment('key', 'split', { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatment('key', 'split', null)).toEqual({ func_attr_bool: false }); // API attributes should be kept in memory and use for evaluations - expect(client.getTreatment('key', 'split', { func_attr_str: 'true' })).toEqual({ func_attr_bool: false, func_attr_str: 'true' }); // API attributes should be kept in memory and use for evaluations + expect(client[method]('key', param, { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Function attributes has precedence against api ones + expect(client[method]('key', param, null)).toEqual({ func_attr_bool: false }); // API attributes should be kept in memory and use for evaluations + expect(client[method]('key', param, { func_attr_str: 'true' })).toEqual({ func_attr_bool: false, func_attr_str: 'true' }); // API attributes should be kept in memory and use for evaluations client.setAttributes({ func_attr_str: 'false' }); expect(client.getAttributes()).toEqual({ 'func_attr_bool': false, 'func_attr_str': 'false' }); // In memory attribute storage must have two stored attributes - expect(client.getTreatment('key', 'split', { func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 })).toEqual({ func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatment('key', 'split', null)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.getTreatment('key', 'split')).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. + expect(client[method]('key', param, { func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 })).toEqual({ func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 }); // Function attributes has precedence against api ones + expect(client[method]('key', param, null)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. + expect(client[method]('key', param)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. expect(client.clearAttributes()).toEqual(true); - - }); - - test('Evaluation attributes logic and precedence / getTreatments', () => { - - // If the same attribute is “cached” and provided on the function, the value received on the function call takes precedence. - expect(client.getTreatments('key', ['split'])).toEqual(undefined); // Nothing changes if no attributes were provided using the new api - expect(client.getTreatments('key', ['split'], { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Nothing changes if no attributes were provided using the new api - expect(client.getAttributes()).toEqual({}); // Attributes in memory storage must be empty - client.setAttribute('func_attr_bool', false); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false }); // In memory attribute storage must have the unique stored attribute - expect(client.getTreatments('key', ['split'], { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatments('key', ['split'], null)).toEqual({ func_attr_bool: false }); // API attributes should be kept in memory and use for evaluations - expect(client.getTreatments('key', ['split'], { func_attr_str: 'true' })).toEqual({ func_attr_bool: false, func_attr_str: 'true' }); // API attributes should be kept in memory and use for evaluations - client.setAttributes({ func_attr_str: 'false' }); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false, 'func_attr_str': 'false' }); // In memory attribute storage must have two stored attributes - expect(client.getTreatments('key', ['split'], { func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 })).toEqual({ func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatments('key', ['split'], null)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.getTreatments('key', ['split'])).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.clearAttributes()).toEqual(true); - - }); - - test('Evaluation attributes logic and precedence / getTreatmentWithConfig', () => { - - // If the same attribute is “cached” and provided on the function, the value received on the function call takes precedence. - expect(client.getTreatmentWithConfig('key', 'split')).toEqual(undefined); // Nothing changes if no attributes were provided using the new api - expect(client.getTreatmentWithConfig('key', 'split', { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Nothing changes if no attributes were provided using the new api - expect(client.getAttributes()).toEqual({}); // Attributes in memory storage must be empty - client.setAttribute('func_attr_bool', false); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false }); // In memory attribute storage must have the unique stored attribute - expect(client.getTreatmentWithConfig('key', 'split', { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentWithConfig('key', 'split', null)).toEqual({ func_attr_bool: false }); // API attributes should be kept in memory and use for evaluations - expect(client.getTreatmentWithConfig('key', 'split', { func_attr_str: 'true' })).toEqual({ func_attr_bool: false, func_attr_str: 'true' }); // API attributes should be kept in memory and use for evaluations - client.setAttributes({ func_attr_str: 'false' }); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false, 'func_attr_str': 'false' }); // In memory attribute storage must have two stored attributes - expect(client.getTreatmentWithConfig('key', 'split', { func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 })).toEqual({ func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentWithConfig('key', 'split', null)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.getTreatmentWithConfig('key', 'split')).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.clearAttributes()).toEqual(true); - - }); - - test('Evaluation attributes logic and precedence / getTreatmentsWithConfig', () => { - - // If the same attribute is “cached” and provided on the function, the value received on the function call takes precedence. - expect(client.getTreatmentsWithConfig('key', ['split'])).toEqual(undefined); // Nothing changes if no attributes were provided using the new api - expect(client.getTreatmentsWithConfig('key', ['split'], { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Nothing changes if no attributes were provided using the new api - expect(client.getAttributes()).toEqual({}); // Attributes in memory storage must be empty - client.setAttribute('func_attr_bool', false); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false }); // In memory attribute storage must have the unique stored attribute - expect(client.getTreatmentsWithConfig('key', ['split'], { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsWithConfig('key', ['split'], null)).toEqual({ func_attr_bool: false }); // API attributes should be kept in memory and use for evaluations - expect(client.getTreatmentsWithConfig('key', ['split'], { func_attr_str: 'true' })).toEqual({ func_attr_bool: false, func_attr_str: 'true' }); // API attributes should be kept in memory and use for evaluations - client.setAttributes({ func_attr_str: 'false' }); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false, 'func_attr_str': 'false' }); // In memory attribute storage must have two stored attributes - expect(client.getTreatmentsWithConfig('key', ['split'], { func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 })).toEqual({ func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsWithConfig('key', ['split'], null)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.getTreatmentsWithConfig('key', ['split'])).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - client.clearAttributes(); - - }); - - test('Evaluation attributes logic and precedence / getTreatmentsByFlagSets', () => { - - // If the same attribute is “cached” and provided on the function, the value received on the function call takes precedence. - expect(client.getTreatmentsByFlagSets('key', ['set'])).toEqual(undefined); // Nothing changes if no attributes were provided using the new api - expect(client.getTreatmentsByFlagSets('key', ['set'], { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Nothing changes if no attributes were provided using the new api - expect(client.getAttributes()).toEqual({}); // Attributes in memory storage must be empty - client.setAttribute('func_attr_bool', false); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false }); // In memory attribute storage must have the unique stored attribute - expect(client.getTreatmentsByFlagSets('key', ['set'], { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsByFlagSets('key', ['set'], null)).toEqual({ func_attr_bool: false }); // API attributes should be kept in memory and use for evaluations - expect(client.getTreatmentsByFlagSets('key', ['set'], { func_attr_str: 'true' })).toEqual({ func_attr_bool: false, func_attr_str: 'true' }); // API attributes should be kept in memory and use for evaluations - client.setAttributes({ func_attr_str: 'false' }); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false, 'func_attr_str': 'false' }); // In memory attribute storage must have two stored attributes - expect(client.getTreatmentsByFlagSets('key', ['set'], { func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 })).toEqual({ func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsByFlagSets('key', ['set'], null)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.getTreatmentsByFlagSets('key', ['set'])).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - client.clearAttributes(); - - }); - - test('Evaluation attributes logic and precedence / getTreatmentsWithConfigByFlagSets', () => { - - // If the same attribute is “cached” and provided on the function, the value received on the function call takes precedence. - expect(client.getTreatmentsWithConfigByFlagSets('key', ['set'])).toEqual(undefined); // Nothing changes if no attributes were provided using the new api - expect(client.getTreatmentsWithConfigByFlagSets('key', ['set'], { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Nothing changes if no attributes were provided using the new api - expect(client.getAttributes()).toEqual({}); // Attributes in memory storage must be empty - client.setAttribute('func_attr_bool', false); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false }); // In memory attribute storage must have the unique stored attribute - expect(client.getTreatmentsWithConfigByFlagSets('key', ['set'], { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsWithConfigByFlagSets('key', ['set'], null)).toEqual({ func_attr_bool: false }); // API attributes should be kept in memory and use for evaluations - expect(client.getTreatmentsWithConfigByFlagSets('key', ['set'], { func_attr_str: 'true' })).toEqual({ func_attr_bool: false, func_attr_str: 'true' }); // API attributes should be kept in memory and use for evaluations - client.setAttributes({ func_attr_str: 'false' }); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false, 'func_attr_str': 'false' }); // In memory attribute storage must have two stored attributes - expect(client.getTreatmentsWithConfigByFlagSets('key', ['set'], { func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 })).toEqual({ func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsWithConfigByFlagSets('key', ['set'], null)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.getTreatmentsWithConfigByFlagSets('key', ['set'])).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - client.clearAttributes(); - - }); - - test('Evaluation attributes logic and precedence / getTreatmentsByFlagSet', () => { - - // If the same attribute is “cached” and provided on the function, the value received on the function call takes precedence. - expect(client.getTreatmentsByFlagSet('key', 'set')).toEqual(undefined); // Nothing changes if no attributes were provided using the new api - expect(client.getTreatmentsByFlagSet('key', 'set', { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Nothing changes if no attributes were provided using the new api - expect(client.getAttributes()).toEqual({}); // Attributes in memory storage must be empty - client.setAttribute('func_attr_bool', false); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false }); // In memory attribute storage must have the unique stored attribute - expect(client.getTreatmentsByFlagSet('key', 'set', { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsByFlagSet('key', 'set', null)).toEqual({ func_attr_bool: false }); // API attributes should be kept in memory and use for evaluations - expect(client.getTreatmentsByFlagSet('key', 'set', { func_attr_str: 'true' })).toEqual({ func_attr_bool: false, func_attr_str: 'true' }); // API attributes should be kept in memory and use for evaluations - client.setAttributes({ func_attr_str: 'false' }); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false, 'func_attr_str': 'false' }); // In memory attribute storage must have two stored attributes - expect(client.getTreatmentsByFlagSet('key', 'set', { func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 })).toEqual({ func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsByFlagSet('key', 'set', null)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.getTreatmentsByFlagSet('key', 'set')).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - client.clearAttributes(); - - }); - - test('Evaluation attributes logic and precedence / getTreatmentsWithConfigByFlagSet', () => { - - // If the same attribute is “cached” and provided on the function, the value received on the function call takes precedence. - expect(client.getTreatmentsWithConfigByFlagSet('key', 'set')).toEqual(undefined); // Nothing changes if no attributes were provided using the new api - expect(client.getTreatmentsWithConfigByFlagSet('key', 'set', { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Nothing changes if no attributes were provided using the new api - expect(client.getAttributes()).toEqual({}); // Attributes in memory storage must be empty - client.setAttribute('func_attr_bool', false); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false }); // In memory attribute storage must have the unique stored attribute - expect(client.getTreatmentsWithConfigByFlagSet('key', 'set', { func_attr_bool: true, func_attr_str: 'true' })).toEqual({ func_attr_bool: true, func_attr_str: 'true' }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsWithConfigByFlagSet('key', 'set', null)).toEqual({ func_attr_bool: false }); // API attributes should be kept in memory and use for evaluations - expect(client.getTreatmentsWithConfigByFlagSet('key', 'set', { func_attr_str: 'true' })).toEqual({ func_attr_bool: false, func_attr_str: 'true' }); // API attributes should be kept in memory and use for evaluations - client.setAttributes({ func_attr_str: 'false' }); - expect(client.getAttributes()).toEqual({ 'func_attr_bool': false, 'func_attr_str': 'false' }); // In memory attribute storage must have two stored attributes - expect(client.getTreatmentsWithConfigByFlagSet('key', 'set', { func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 })).toEqual({ func_attr_bool: true, func_attr_str: 'true', func_attr_number: 1 }); // Function attributes has precedence against api ones - // @ts-ignore - expect(client.getTreatmentsWithConfigByFlagSet('key', 'set', null)).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - expect(client.getTreatmentsWithConfigByFlagSet('key', 'set')).toEqual({ func_attr_bool: false, func_attr_str: 'false' }); // If the getTreatment function is called without attributes, stored attributes will be used to evaluate. - client.clearAttributes(); - }); }); diff --git a/src/sdkClient/clientAttributesDecoration.ts b/src/sdkClient/clientAttributesDecoration.ts index 388b3142..b9a901db 100644 --- a/src/sdkClient/clientAttributesDecoration.ts +++ b/src/sdkClient/clientAttributesDecoration.ts @@ -20,7 +20,6 @@ export function clientAttributesDecoration 0 ? @@ -74,7 +69,6 @@ export function clientAttributesDecoration Date: Thu, 20 Mar 2025 16:40:31 -0300 Subject: [PATCH 06/13] Updated CHANGES.txt file and additional polishing --- CHANGES.txt | 3 + package-lock.json | 307 ++++++++++------------------------------ src/sdkClient/client.ts | 22 +-- types/splitio.d.ts | 24 ++++ 4 files changed, 112 insertions(+), 244 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index db1fa81e..4420de06 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.2.0 (March 26, 2025) + - Added new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impression object sent to Split's backend. Read more in our docs. + 2.1.0 (January 17, 2025) - Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on `SplitView` type objects. Read more in our docs. diff --git a/package-lock.json b/package-lock.json index f15fce99..f559a07a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,13 +67,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -266,18 +267,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -293,38 +294,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", - "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", + "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "node_modules/@babel/parser": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", + "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/types": "^7.26.10" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -495,9 +484,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", - "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -507,14 +496,14 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" @@ -542,14 +531,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", + "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1979,18 +1967,6 @@ "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2379,20 +2355,6 @@ } ] }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -2459,21 +2421,6 @@ "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2812,15 +2759,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/escodegen": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", @@ -3978,15 +3916,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", @@ -7465,18 +7394,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/supports-hyperlinks": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", @@ -7577,15 +7494,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8164,13 +8072,14 @@ } }, "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "requires": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" } }, "@babel/compat-data": { @@ -8319,15 +8228,15 @@ } }, "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true }, "@babel/helper-validator-option": { @@ -8337,33 +8246,24 @@ "dev": true }, "@babel/helpers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", - "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", + "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", "dev": true, "requires": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10" } }, - "@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "@babel/parser": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", + "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/types": "^7.26.10" } }, - "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", - "dev": true - }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -8482,23 +8382,23 @@ } }, "@babel/runtime": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", - "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", "dev": true, "requires": { "regenerator-runtime": "^0.14.0" } }, "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" } }, "@babel/traverse": { @@ -8520,14 +8420,13 @@ } }, "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", + "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" } }, "@bcoe/v8-coverage": { @@ -9608,15 +9507,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -9898,17 +9788,6 @@ "integrity": "sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==", "dev": true }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -9956,21 +9835,6 @@ "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", "dev": true }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -10228,12 +10092,6 @@ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, "escodegen": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", @@ -11089,12 +10947,6 @@ "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", "dev": true }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", @@ -13670,15 +13522,6 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, "supports-hyperlinks": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", @@ -13757,12 +13600,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index ab3b39e6..84411c6d 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -23,6 +23,10 @@ function treatmentsNotReady(featureFlagNames: string[]) { return evaluations; } +function stringify(options?: SplitIO.EvaluationOptions) { + return options && options.properties ? JSON.stringify(options.properties) : undefined; +} + /** * Creator of base client with getTreatments and track methods. */ @@ -36,7 +40,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const wrapUp = (evaluationResult: IEvaluationResult) => { const queue: ImpressionDecorated[] = []; - const treatment = processEvaluation(evaluationResult, featureFlagName, key, options, withConfig, methodName, queue); + const treatment = processEvaluation(evaluationResult, featureFlagName, key, stringify(options), withConfig, methodName, queue); impressionsTracker.track(queue, attributes); stopTelemetryTracker(queue[0] && queue[0].imp.label); @@ -61,9 +65,10 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const wrapUp = (evaluationResults: Record) => { const queue: ImpressionDecorated[] = []; - const treatments: Record = {}; + const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {}; + const properties = stringify(options); Object.keys(evaluationResults).forEach(featureFlagName => { - treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, options, withConfig, methodName, queue); + treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue); }); impressionsTracker.track(queue, attributes); @@ -89,10 +94,10 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const wrapUp = (evaluationResults: Record) => { const queue: ImpressionDecorated[] = []; - const treatments: Record = {}; - const evaluations = evaluationResults; - Object.keys(evaluations).forEach(featureFlagName => { - treatments[featureFlagName] = processEvaluation(evaluations[featureFlagName], featureFlagName, key, options, withConfig, methodName, queue); + const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {}; + const properties = stringify(options); + Object.keys(evaluationResults).forEach(featureFlagName => { + treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue); }); impressionsTracker.track(queue, attributes); @@ -126,14 +131,13 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl evaluation: IEvaluationResult, featureFlagName: string, key: SplitIO.SplitKey, - options: SplitIO.EvaluationOptions | undefined, + properties: string | undefined, withConfig: boolean, invokingMethodName: string, queue: ImpressionDecorated[] ): SplitIO.Treatment | SplitIO.TreatmentWithConfig { const matchingKey = getMatching(key); const bucketingKey = getBucketing(key); - const properties = options && options.properties ? JSON.stringify(options.properties) : undefined; const { treatment, label, changeNumber, config = null, impressionsDisabled } = evaluation; log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]); diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 5aff3a88..75232c28 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -809,13 +809,37 @@ declare namespace SplitIO { * Impression DTO generated by the SDK when processing evaluations. */ type ImpressionDTO = { + /** + * Feature flag name. + */ feature: string; + /** + * Key. + */ keyName: string; + /** + * Treatment value. + */ treatment: string; + /** + * Impression timestamp. + */ time: number; + /** + * Bucketing Key + */ bucketingKey?: string; + /** + * Rule label + */ label: string; + /** + * Version of the feature flag + */ changeNumber: number; + /** + * Previous time + */ pt?: number; /** * JSON stringified version of the impression properties. From d66c7fcad0d52233de84c8cdd0e5543b1b497849 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 20 Mar 2025 16:52:47 -0300 Subject: [PATCH 07/13] Update type definitions --- types/splitio.d.ts | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 75232c28..739fd85a 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -1548,6 +1548,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param featureFlagName - The string that represents the feature flag we want to get the treatment. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The treatment string. */ getTreatment(key: SplitKey, featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): Treatment; @@ -1557,8 +1558,8 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param featureFlagName - The string that represents the feature flag we want to get the treatment. * @param attributes - An object of type Attributes defining the attributes for the given key. - * @returns The TreatmentWithConfig, the object containing the treatment string and the - * configuration stringified JSON (or null if there was no config for that treatment). + * @param options - An object of type EvaluationOptions for advanced evaluation options. + * @returns The TreatmentWithConfig object that contains the treatment string and the configuration stringified JSON (or null if there was no config for that treatment). */ getTreatmentWithConfig(key: SplitKey, featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): TreatmentWithConfig; /** @@ -1567,6 +1568,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param featureFlagNames - An array of the feature flag names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The treatments object map. */ getTreatments(key: SplitKey, featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): Treatments; @@ -1576,6 +1578,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param featureFlagNames - An array of the feature flag names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the TreatmentWithConfig objects */ getTreatmentsWithConfig(key: SplitKey, featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; @@ -1585,6 +1588,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param flagSet - The flag set name we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the Treatment objects */ getTreatmentsByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes, options?: EvaluationOptions): Treatments; @@ -1594,6 +1598,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param flagSet - The flag set name we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the TreatmentWithConfig objects */ getTreatmentsWithConfigByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; @@ -1603,6 +1608,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param flagSets - An array of the flag set names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the Treatment objects */ getTreatmentsByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): Treatments; @@ -1612,6 +1618,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param flagSets - An array of the flag set names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the TreatmentWithConfig objects */ getTreatmentsWithConfigByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; @@ -1639,6 +1646,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param featureFlagName - The string that represents the feature flag we want to get the treatment. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns Treatment promise that resolves to the treatment string. */ getTreatment(key: SplitKey, featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatment; @@ -1648,6 +1656,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param featureFlagName - The string that represents the feature flag we want to get the treatment. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns TreatmentWithConfig promise that resolves to the TreatmentWithConfig object. */ getTreatmentWithConfig(key: SplitKey, featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentWithConfig; @@ -1657,6 +1666,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param featureFlagNames - An array of the feature flag names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns Treatments promise that resolves to the treatments object map. */ getTreatments(key: SplitKey, featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; @@ -1666,6 +1676,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param featureFlagNames - An array of the feature flag names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns TreatmentsWithConfig promise that resolves to the map of TreatmentsWithConfig objects. */ getTreatmentsWithConfig(key: SplitKey, featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; @@ -1675,6 +1686,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param flagSet - The flag set name we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns Treatments promise that resolves to the treatments object map. */ getTreatmentsByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; @@ -1684,6 +1696,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param flagSet - The flag set name we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns TreatmentsWithConfig promise that resolves to the map of TreatmentsWithConfig objects. */ getTreatmentsWithConfigByFlagSet(key: SplitKey, flagSet: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; @@ -1693,6 +1706,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param flagSets - An array of the flag set names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns Treatments promise that resolves to the treatments object map. */ getTreatmentsByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; @@ -1702,6 +1716,7 @@ declare namespace SplitIO { * @param key - The string key representing the consumer. * @param flagSets - An array of the flag set names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns TreatmentsWithConfig promise that resolves to the map of TreatmentsWithConfig objects. */ getTreatmentsWithConfigByFlagSets(key: SplitKey, flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; @@ -1769,6 +1784,7 @@ declare namespace SplitIO { * * @param featureFlagName - The string that represents the feature flag we want to get the treatment. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The treatment string. */ getTreatment(featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): Treatment; @@ -1777,7 +1793,8 @@ declare namespace SplitIO { * * @param featureFlagName - The string that represents the feature flag we want to get the treatment. * @param attributes - An object of type Attributes defining the attributes for the given key. - * @returns The map containing the treatment and the configuration stringified JSON (or null if there was no config for that treatment). + * @param options - An object of type EvaluationOptions for advanced evaluation options. + * @returns The TreatmentWithConfig object that contains the treatment string and the configuration stringified JSON (or null if there was no config for that treatment). */ getTreatmentWithConfig(featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): TreatmentWithConfig; /** @@ -1785,6 +1802,7 @@ declare namespace SplitIO { * * @param featureFlagNames - An array of the feature flag names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The treatments object map. */ getTreatments(featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): Treatments; @@ -1793,6 +1811,7 @@ declare namespace SplitIO { * * @param featureFlagNames - An array of the feature flag names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the TreatmentWithConfig objects */ getTreatmentsWithConfig(featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; @@ -1801,6 +1820,7 @@ declare namespace SplitIO { * * @param flagSet - The flag set name we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the Treatments objects */ getTreatmentsByFlagSet(flagSet: string, attributes?: Attributes, options?: EvaluationOptions): Treatments; @@ -1809,6 +1829,7 @@ declare namespace SplitIO { * * @param flagSet - The flag set name we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the TreatmentWithConfig objects */ getTreatmentsWithConfigByFlagSet(flagSet: string, attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; @@ -1817,6 +1838,7 @@ declare namespace SplitIO { * * @param flagSets - An array of the flag set names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the Treatments objects */ getTreatmentsByFlagSets(flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): Treatments; @@ -1825,6 +1847,7 @@ declare namespace SplitIO { * * @param flagSets - An array of the flag set names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns The map with all the TreatmentWithConfig objects */ getTreatmentsWithConfigByFlagSets(flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): TreatmentsWithConfig; @@ -1848,6 +1871,7 @@ declare namespace SplitIO { * * @param featureFlagName - The string that represents the feature flag we want to get the treatment. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns Treatment promise that resolves to the treatment string. */ getTreatment(featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatment; @@ -1856,6 +1880,7 @@ declare namespace SplitIO { * * @param featureFlagName - The string that represents the feature flag we want to get the treatment. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns TreatmentWithConfig promise that resolves to the TreatmentWithConfig object. */ getTreatmentWithConfig(featureFlagName: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentWithConfig; @@ -1864,6 +1889,7 @@ declare namespace SplitIO { * * @param featureFlagNames - An array of the feature flag names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns Treatments promise that resolves to the treatments object map. */ getTreatments(featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; @@ -1872,6 +1898,7 @@ declare namespace SplitIO { * * @param featureFlagNames - An array of the feature flag names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns TreatmentsWithConfig promise that resolves to the TreatmentsWithConfig object. */ getTreatmentsWithConfig(featureFlagNames: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; @@ -1880,6 +1907,7 @@ declare namespace SplitIO { * * @param flagSet - The flag set name we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns Treatments promise that resolves to the treatments object map. */ getTreatmentsByFlagSet(flagSet: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; @@ -1888,6 +1916,7 @@ declare namespace SplitIO { * * @param flagSet - The flag set name we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns TreatmentsWithConfig promise that resolves to the TreatmentsWithConfig object. */ getTreatmentsWithConfigByFlagSet(flagSet: string, attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; @@ -1896,6 +1925,7 @@ declare namespace SplitIO { * * @param flagSets - An array of the flag set names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns Treatments promise that resolves to the treatments object map. */ getTreatmentsByFlagSets(flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatments; @@ -1904,6 +1934,7 @@ declare namespace SplitIO { * * @param flagSets - An array of the flag set names we want to get the treatments. * @param attributes - An object of type Attributes defining the attributes for the given key. + * @param options - An object of type EvaluationOptions for advanced evaluation options. * @returns TreatmentsWithConfig promise that resolves to the TreatmentsWithConfig object. */ getTreatmentsWithConfigByFlagSets(flagSets: string[], attributes?: Attributes, options?: EvaluationOptions): AsyncTreatmentsWithConfig; From bffcdaf453db62d39d60b71bca21e234461c44ce Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 25 Mar 2025 12:05:20 -0300 Subject: [PATCH 08/13] Add error handling --- src/sdkClient/client.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index 84411c6d..0e526f72 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -24,7 +24,11 @@ function treatmentsNotReady(featureFlagNames: string[]) { } function stringify(options?: SplitIO.EvaluationOptions) { - return options && options.properties ? JSON.stringify(options.properties) : undefined; + if (options && options.properties) { + try { + return JSON.stringify(options.properties); + } catch { /* JSON.stringify should never throw with validated options, but handling just in case */ } + } } /** From 491ed3443226802ceb4bc28c4fdf9ce33f72c189 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 25 Mar 2025 18:08:05 -0300 Subject: [PATCH 09/13] Enhance input validation to ignore empty object properties --- src/sdkClient/__tests__/clientInputValidation.spec.ts | 10 ++++++++++ src/utils/inputValidation/eventProperties.ts | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/sdkClient/__tests__/clientInputValidation.spec.ts b/src/sdkClient/__tests__/clientInputValidation.spec.ts index 25b224e6..f70845f7 100644 --- a/src/sdkClient/__tests__/clientInputValidation.spec.ts +++ b/src/sdkClient/__tests__/clientInputValidation.spec.ts @@ -94,4 +94,14 @@ describe('clientInputValidationDecorator', () => { expect(logSpy).toHaveBeenLastCalledWith('[WARN] splitio => getTreatment: Property "toSanitize" is of invalid type. Setting value to null.'); expect(client.getTreatment).toBeCalledWith('key', 'ff', undefined, { properties: { toSanitize: null, correct: 100 }}); }); + + test('should ignore the properties in the 4th argument if an empty object is passed', () => { + expect(clientWithValidation.getTreatment('key', 'ff', undefined, { properties: {} })).toBe(EVALUATION_RESULT); + expect(client.getTreatment).toHaveBeenLastCalledWith('key', 'ff', undefined, undefined); + + expect(clientWithValidation.getTreatment('key', 'ff', undefined, { properties: undefined })).toBe(EVALUATION_RESULT); + expect(client.getTreatment).toHaveBeenLastCalledWith('key', 'ff', undefined, undefined); + + expect(logSpy).not.toBeCalled(); + }); }); diff --git a/src/utils/inputValidation/eventProperties.ts b/src/utils/inputValidation/eventProperties.ts index 63178419..1306431c 100644 --- a/src/utils/inputValidation/eventProperties.ts +++ b/src/utils/inputValidation/eventProperties.ts @@ -70,7 +70,7 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth export function validateEvaluationOptions(log: ILogger, maybeOptions: any, method: string): SplitIO.EvaluationOptions | undefined { if (isObject(maybeOptions)) { const properties = validateEventProperties(log, maybeOptions.properties, method).properties; - return properties ? { properties } : undefined; + return properties && Object.keys(properties).length > 0 ? { properties } : undefined; } else if (maybeOptions) { log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'evaluation options']); } From 23f49090ee2b82b42036cb769b07c2683ff64516 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 26 Mar 2025 23:03:15 -0300 Subject: [PATCH 10/13] Impression properties in consumer mode --- src/storages/utils.ts | 1 + src/sync/submitters/impressionsSubmitter.ts | 2 +- src/sync/submitters/types.ts | 58 ++++++++------------- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/storages/utils.ts b/src/storages/utils.ts index 2963bbc5..49b21690 100644 --- a/src/storages/utils.ts +++ b/src/storages/utils.ts @@ -30,6 +30,7 @@ export function impressionsToJSON(impressions: SplitIO.ImpressionDTO[], metadata c: impression.changeNumber, m: impression.time, pt: impression.pt, + properties: impression.properties } }; diff --git a/src/sync/submitters/impressionsSubmitter.ts b/src/sync/submitters/impressionsSubmitter.ts index fed33e0f..bf05a587 100644 --- a/src/sync/submitters/impressionsSubmitter.ts +++ b/src/sync/submitters/impressionsSubmitter.ts @@ -27,7 +27,7 @@ export function fromImpressionsCollector(sendLabels: boolean, data: SplitIO.Impr r: sendLabels ? entry.label : undefined, // Rule b: entry.bucketingKey, // Bucketing Key pt: entry.pt, // Previous time - properties: entry.properties && JSON.stringify(entry.properties) // Properties + properties: entry.properties // Properties }; return keyImpression; diff --git a/src/sync/submitters/types.ts b/src/sync/submitters/types.ts index 47e35c07..9bae212e 100644 --- a/src/sync/submitters/types.ts +++ b/src/sync/submitters/types.ts @@ -3,28 +3,30 @@ import { IMetadata } from '../../dtos/types'; import SplitIO from '../../../types/splitio'; import { ISyncTask } from '../types'; +type ImpressionPayload = { + /** Matching Key */ + k: string; + /** Bucketing Key */ + b?: string; + /** Treatment */ + t: string; + /** Timestamp */ + m: number; + /** Change number */ + c: number; + /** Rule label */ + r?: string; + /** Previous time */ + pt?: number; + /** Stringified JSON object with properties */ + properties?: string; +}; + export type ImpressionsPayload = { /** Split name */ f: string, /** Key Impressions */ - i: { - /** User Key */ - k: string; - /** Treatment */ - t: string; - /** Timestamp */ - m: number; - /** ChangeNumber */ - c: number; - /** Rule label */ - r?: string; - /** Bucketing Key */ - b?: string; - /** Previous time */ - pt?: number; - /** Stringified JSON object with properties */ - properties?: string; - }[] + i: ImpressionPayload[] }[] export type ImpressionCountsPayload = { @@ -62,23 +64,9 @@ export type StoredImpressionWithMetadata = { /** Metadata */ m: IMetadata, /** Stored impression */ - i: { - /** keyName */ - k: string, - /** bucketingKey */ - b?: string, - /** Split name */ - f: string, - /** treatment */ - t: string, - /** label */ - r: string, - /** changeNumber */ - c: number, - /** time */ - m: number - /** previous time */ - pt?: number + i: ImpressionPayload & { + /** Feature flag name */ + f: string } } From 0abe3b225cd1bb5892f91cdbc7fff0ecaf91f85f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 27 Mar 2025 13:24:10 -0300 Subject: [PATCH 11/13] rc --- CHANGES.txt | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4420de06..41924a72 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -2.2.0 (March 26, 2025) +2.2.0 (March 28, 2025) - Added new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impression object sent to Split's backend. Read more in our docs. 2.1.0 (January 17, 2025) diff --git a/package-lock.json b/package-lock.json index f559a07a..4cccfff2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.1.0", + "version": "2.1.1-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "2.1.0", + "version": "2.1.1-rc.1", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", diff --git a/package.json b/package.json index d51bc14f..5f6a44d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "2.1.0", + "version": "2.1.1-rc.1", "description": "Split JavaScript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", From 89c14f0020c21c6381aa8b1fa4587ad693e6b468 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 27 Mar 2025 15:36:19 -0300 Subject: [PATCH 12/13] Update type definitions --- CHANGES.txt | 2 +- types/splitio.d.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index 41924a72..29391a11 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,5 @@ 2.2.0 (March 28, 2025) - - Added new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impression object sent to Split's backend. Read more in our docs. + - Added new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impression object sent to Split backend. 2.1.0 (January 17, 2025) - Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on `SplitView` type objects. Read more in our docs. diff --git a/types/splitio.d.ts b/types/splitio.d.ts index 739fd85a..4102a7fc 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -782,6 +782,9 @@ declare namespace SplitIO { * Evaluation options object for getTreatment methods. */ type EvaluationOptions = { + /** + * Optional properties to append to the generated impression object sent to Split backend. + */ properties?: Properties; } /** From 9aebb370ebeee4431a2cc91fe94d7c5d778d82f2 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 28 Mar 2025 12:02:23 -0300 Subject: [PATCH 13/13] Update DEBUG strategy --- src/trackers/strategy/strategyDebug.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/trackers/strategy/strategyDebug.ts b/src/trackers/strategy/strategyDebug.ts index 65bc06b3..ae19973e 100644 --- a/src/trackers/strategy/strategyDebug.ts +++ b/src/trackers/strategy/strategyDebug.ts @@ -14,6 +14,8 @@ export function strategyDebugFactory( return { process(impression: SplitIO.ImpressionDTO) { + if (impression.properties) return true; + impression.pt = impressionsObserver.testAndSet(impression); return true; }