Skip to content

Commit fad235b

Browse files
Add impression properties support
1 parent 6e7d790 commit fad235b

12 files changed

Lines changed: 103 additions & 75 deletions

File tree

src/logger/messages/error.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const codesError: [number, string][] = [
2121
// input validation
2222
[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.'],
2323
[c.ERROR_NOT_PLAIN_OBJECT, '%s: %s must be a plain object.'],
24-
[c.ERROR_SIZE_EXCEEDED, '%s: the maximum size allowed for the properties is 32768 bytes, which was exceeded. Event not queued.'],
24+
[c.ERROR_SIZE_EXCEEDED, '%s: the maximum size allowed for the properties is 32768 bytes, which was exceeded.'],
2525
[c.ERROR_NOT_FINITE, '%s: value must be a finite number.'],
2626
[c.ERROR_NULL, '%s: you passed a null or undefined %s. It must be a non-empty string.'],
2727
[c.ERROR_TOO_LONG, '%s: %s too long. It must have 250 characters or less.'],

src/sdkClient/client.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
5656
return getTreatment(key, featureFlagName, attributes, true, GET_TREATMENT_WITH_CONFIG);
5757
}
5858

59-
function getTreatments(key: SplitIO.SplitKey, featureFlagNames: string[], attributes: SplitIO.Attributes | undefined, withConfig = false, methodName = GET_TREATMENTS) {
59+
function getTreatments(key: SplitIO.SplitKey, featureFlagNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions, withConfig = false, methodName = GET_TREATMENTS) {
6060
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);
6161

6262
const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
@@ -65,7 +65,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
6565
Object.keys(evaluationResults).forEach(featureFlagName => {
6666
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue);
6767
});
68-
impressionsTracker.track(queue, attributes);
68+
impressionsTracker.track(queue, attributes, options);
6969

7070
stopTelemetryTracker(queue[0] && queue[0].imp.label);
7171
return treatments;
@@ -80,11 +80,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
8080
return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations);
8181
}
8282

83-
function getTreatmentsWithConfig(key: SplitIO.SplitKey, featureFlagNames: string[], attributes: SplitIO.Attributes | undefined) {
84-
return getTreatments(key, featureFlagNames, attributes, true, GET_TREATMENTS_WITH_CONFIG);
83+
function getTreatmentsWithConfig(key: SplitIO.SplitKey, featureFlagNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) {
84+
return getTreatments(key, featureFlagNames, attributes, options, true, GET_TREATMENTS_WITH_CONFIG);
8585
}
8686

87-
function getTreatmentsByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes: SplitIO.Attributes | undefined, withConfig = false, method: Method = TREATMENTS_BY_FLAGSETS, methodName = GET_TREATMENTS_BY_FLAG_SETS) {
87+
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) {
8888
const stopTelemetryTracker = telemetryTracker.trackEval(method);
8989

9090
const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
@@ -94,7 +94,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
9494
Object.keys(evaluations).forEach(featureFlagName => {
9595
treatments[featureFlagName] = processEvaluation(evaluations[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue);
9696
});
97-
impressionsTracker.track(queue, attributes);
97+
impressionsTracker.track(queue, attributes, options);
9898

9999
stopTelemetryTracker(queue[0] && queue[0].imp.label);
100100
return treatments;
@@ -109,16 +109,16 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
109109
return thenable(evaluations) ? evaluations.then((res) => wrapUp(res)) : wrapUp(evaluations);
110110
}
111111

112-
function getTreatmentsWithConfigByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes: SplitIO.Attributes | undefined) {
113-
return getTreatmentsByFlagSets(key, flagSetNames, attributes, true, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
112+
function getTreatmentsWithConfigByFlagSets(key: SplitIO.SplitKey, flagSetNames: string[], attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) {
113+
return getTreatmentsByFlagSets(key, flagSetNames, attributes, options, true, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
114114
}
115115

116-
function getTreatmentsByFlagSet(key: SplitIO.SplitKey, flagSetName: string, attributes: SplitIO.Attributes | undefined) {
117-
return getTreatmentsByFlagSets(key, [flagSetName], attributes, false, TREATMENTS_BY_FLAGSET, GET_TREATMENTS_BY_FLAG_SET);
116+
function getTreatmentsByFlagSet(key: SplitIO.SplitKey, flagSetName: string, attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) {
117+
return getTreatmentsByFlagSets(key, [flagSetName], attributes, options, false, TREATMENTS_BY_FLAGSET, GET_TREATMENTS_BY_FLAG_SET);
118118
}
119119

120-
function getTreatmentsWithConfigByFlagSet(key: SplitIO.SplitKey, flagSetName: string, attributes: SplitIO.Attributes | undefined) {
121-
return getTreatmentsByFlagSets(key, [flagSetName], attributes, true, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
120+
function getTreatmentsWithConfigByFlagSet(key: SplitIO.SplitKey, flagSetName: string, attributes?: SplitIO.Attributes, options?: SplitIO.EvaluationOptions) {
121+
return getTreatmentsByFlagSets(key, [flagSetName], attributes, options, true, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
122122
}
123123

124124
// Internal function

src/sdkClient/clientInputValidation.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
validateSplits,
1010
validateTrafficType,
1111
validateIfNotDestroyed,
12-
validateIfOperational
12+
validateIfOperational,
13+
validateEvaluationOptions
1314
} from '../utils/inputValidation';
1415
import { startsWith } from '../utils/lang';
1516
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<TClient extends SplitIO.IClient |
3233
/**
3334
* Avoid repeating this validations code
3435
*/
35-
function validateEvaluationParams(maybeKey: SplitIO.SplitKey, maybeNameOrNames: string | string[], maybeAttributes: SplitIO.Attributes | undefined, methodName: string) {
36+
function validateEvaluationParams(methodName: string, maybeKey: SplitIO.SplitKey, maybeNameOrNames: string | string[], maybeAttributes?: SplitIO.Attributes, maybeOptions?: SplitIO.EvaluationOptions) {
3637
const key = validateKey(log, maybeKey, methodName);
3738

3839
const nameOrNames = methodName.indexOf('ByFlagSet') > -1 ?
@@ -43,6 +44,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
4344

4445
const attributes = validateAttributes(log, maybeAttributes, methodName);
4546
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);
47+
const options = validateEvaluationOptions(log, maybeOptions, methodName);
4648

4749
validateIfOperational(log, readinessManager, methodName, nameOrNames);
4850

@@ -52,16 +54,17 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
5254
valid,
5355
key,
5456
nameOrNames,
55-
attributes
57+
attributes,
58+
options
5659
};
5760
}
5861

5962
function wrapResult<T>(value: T): MaybeThenable<T> {
6063
return isAsync ? Promise.resolve(value) : value;
6164
}
6265

63-
function getTreatment(maybeKey: SplitIO.SplitKey, maybeFeatureFlagName: string, maybeAttributes?: SplitIO.Attributes) {
64-
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes, GET_TREATMENT);
66+
function getTreatment(maybeKey: SplitIO.SplitKey, maybeFeatureFlagName: string, maybeAttributes?: SplitIO.Attributes, maybeOptions?: SplitIO.EvaluationOptions) {
67+
const params = validateEvaluationParams(GET_TREATMENT, maybeKey, maybeFeatureFlagName, maybeAttributes, maybeOptions);
6568

6669
if (params.valid) {
6770
return client.getTreatment(params.key as SplitIO.SplitKey, params.nameOrNames as string, params.attributes as SplitIO.Attributes | undefined);
@@ -71,7 +74,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
7174
}
7275

7376
function getTreatmentWithConfig(maybeKey: SplitIO.SplitKey, maybeFeatureFlagName: string, maybeAttributes?: SplitIO.Attributes) {
74-
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagName, maybeAttributes, GET_TREATMENT_WITH_CONFIG);
77+
const params = validateEvaluationParams(GET_TREATMENT_WITH_CONFIG, maybeKey, maybeFeatureFlagName, maybeAttributes);
7578

7679
if (params.valid) {
7780
return client.getTreatmentWithConfig(params.key as SplitIO.SplitKey, params.nameOrNames as string, params.attributes as SplitIO.Attributes | undefined);
@@ -81,10 +84,10 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
8184
}
8285

8386
function getTreatments(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNames: string[], maybeAttributes?: SplitIO.Attributes) {
84-
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes, GET_TREATMENTS);
87+
const params = validateEvaluationParams(GET_TREATMENTS, maybeKey, maybeFeatureFlagNames, maybeAttributes);
8588

8689
if (params.valid) {
87-
return client.getTreatments(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined);
90+
return client.getTreatments(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined, );
8891
} else {
8992
const res: SplitIO.Treatments = {};
9093
if (params.nameOrNames) (params.nameOrNames as string[]).forEach((split: string) => res[split] = CONTROL);
@@ -94,7 +97,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
9497
}
9598

9699
function getTreatmentsWithConfig(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNames: string[], maybeAttributes?: SplitIO.Attributes) {
97-
const params = validateEvaluationParams(maybeKey, maybeFeatureFlagNames, maybeAttributes, GET_TREATMENTS_WITH_CONFIG);
100+
const params = validateEvaluationParams(GET_TREATMENTS_WITH_CONFIG, maybeKey, maybeFeatureFlagNames, maybeAttributes);
98101

99102
if (params.valid) {
100103
return client.getTreatmentsWithConfig(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined);
@@ -107,7 +110,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
107110
}
108111

109112
function getTreatmentsByFlagSets(maybeKey: SplitIO.SplitKey, maybeFlagSets: string[], maybeAttributes?: SplitIO.Attributes) {
110-
const params = validateEvaluationParams(maybeKey, maybeFlagSets, maybeAttributes, GET_TREATMENTS_BY_FLAG_SETS);
113+
const params = validateEvaluationParams(GET_TREATMENTS_BY_FLAG_SETS, maybeKey, maybeFlagSets, maybeAttributes, );
111114

112115
if (params.valid) {
113116
return client.getTreatmentsByFlagSets(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined);
@@ -117,7 +120,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
117120
}
118121

119122
function getTreatmentsWithConfigByFlagSets(maybeKey: SplitIO.SplitKey, maybeFlagSets: string[], maybeAttributes?: SplitIO.Attributes) {
120-
const params = validateEvaluationParams(maybeKey, maybeFlagSets, maybeAttributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS);
123+
const params = validateEvaluationParams(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, maybeKey, maybeFlagSets, maybeAttributes);
121124

122125
if (params.valid) {
123126
return client.getTreatmentsWithConfigByFlagSets(params.key as SplitIO.SplitKey, params.nameOrNames as string[], params.attributes as SplitIO.Attributes | undefined);
@@ -127,7 +130,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
127130
}
128131

129132
function getTreatmentsByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes) {
130-
const params = validateEvaluationParams(maybeKey, [maybeFlagSet], maybeAttributes, GET_TREATMENTS_BY_FLAG_SET);
133+
const params = validateEvaluationParams(GET_TREATMENTS_BY_FLAG_SET, maybeKey, [maybeFlagSet], maybeAttributes);
131134

132135
if (params.valid) {
133136
return client.getTreatmentsByFlagSet(params.key as SplitIO.SplitKey, (params.nameOrNames as string[])[0], params.attributes as SplitIO.Attributes | undefined);
@@ -137,7 +140,7 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
137140
}
138141

139142
function getTreatmentsWithConfigByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes) {
140-
const params = validateEvaluationParams(maybeKey, [maybeFlagSet], maybeAttributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET);
143+
const params = validateEvaluationParams(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, maybeKey, [maybeFlagSet], maybeAttributes);
141144

142145
if (params.valid) {
143146
return client.getTreatmentsWithConfigByFlagSet(params.key as SplitIO.SplitKey, (params.nameOrNames as string[])[0], params.attributes as SplitIO.Attributes | undefined);

src/sdkFactory/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,12 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
6262
const uniqueKeysTracker = uniqueKeysTrackerFactory(log, storage.uniqueKeys, filterAdapterFactory && filterAdapterFactory());
6363

6464
const noneStrategy = strategyNoneFactory(storage.impressionCounts, uniqueKeysTracker);
65-
const strategy = impressionsMode === OPTIMIZED ?
65+
const debugStrategy = strategyDebugFactory(observer);
66+
const defaultStrategy = impressionsMode === OPTIMIZED ?
6667
strategyOptimizedFactory(observer, storage.impressionCounts) :
67-
impressionsMode === DEBUG ?
68-
strategyDebugFactory(observer) :
69-
noneStrategy;
68+
impressionsMode === DEBUG ? debugStrategy : noneStrategy;
7069

71-
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, whenInit, integrationsManager, storage.telemetry);
70+
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, debugStrategy, defaultStrategy, whenInit, integrationsManager, storage.telemetry);
7271
const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
7372

7473
// splitApi is used by SyncManager and Browser signal listener

src/sync/submitters/impressionsSubmitter.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ export function fromImpressionsCollector(sendLabels: boolean, data: SplitIO.Impr
2525
m: entry.time, // Timestamp
2626
c: entry.changeNumber, // ChangeNumber
2727
r: sendLabels ? entry.label : undefined, // Rule
28-
b: entry.bucketingKey ? entry.bucketingKey : undefined, // Bucketing Key
29-
pt: entry.pt ? entry.pt : undefined // Previous time
28+
b: entry.bucketingKey, // Bucketing Key
29+
pt: entry.pt, // Previous time
30+
properties: entry.properties && JSON.stringify(entry.properties) // Properties
3031
};
3132

3233
return keyImpression;

src/sync/submitters/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export type ImpressionsPayload = {
2222
b?: string;
2323
/** Previous time */
2424
pt?: number;
25+
/** Stringified JSON object with properties */
26+
properties?: string;
2527
}[]
2628
}[]
2729

src/trackers/__tests__/impressionsTracker.spec.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ const fakeNoneStrategy = {
4040
process: jest.fn(() => false)
4141
};
4242

43+
const fakeDebugStrategy = {
44+
process: jest.fn(() => false)
45+
};
46+
4347
/* Tests */
4448

4549
describe('Impressions Tracker', () => {
@@ -53,7 +57,7 @@ describe('Impressions Tracker', () => {
5357
const strategy = strategyDebugFactory(impressionObserverCSFactory());
5458

5559
test('Should be able to track impressions (in DEBUG mode without Previous Time).', () => {
56-
const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit);
60+
const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategy, fakeWhenInit);
5761

5862
const imp1 = {
5963
feature: '10',
@@ -73,7 +77,7 @@ describe('Impressions Tracker', () => {
7377
});
7478

7579
test('Tracked impressions should be sent to impression listener and integration manager when we invoke .track()', (done) => {
76-
const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit, fakeIntegrationsManager);
80+
const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategy, fakeWhenInit, fakeIntegrationsManager);
7781

7882
const fakeImpression = {
7983
feature: 'impression'
@@ -147,8 +151,8 @@ describe('Impressions Tracker', () => {
147151
impression3.time = 1234567891;
148152

149153
const trackers = [
150-
impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverSSFactory()), fakeWhenInit, undefined),
151-
impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverCSFactory()), fakeWhenInit, undefined)
154+
impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategyDebugFactory(impressionObserverSSFactory()), fakeWhenInit, undefined),
155+
impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategyDebugFactory(impressionObserverCSFactory()), fakeWhenInit, undefined)
152156
];
153157

154158
expect(fakeImpressionsCache.track).not.toBeCalled(); // storage method should not be called until impressions are tracked.
@@ -175,7 +179,7 @@ describe('Impressions Tracker', () => {
175179
impression3.time = Date.now();
176180

177181
const impressionCountsCache = new ImpressionCountsCacheInMemory();
178-
const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), fakeWhenInit, undefined, fakeTelemetryCache as any);
182+
const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), fakeWhenInit, undefined, fakeTelemetryCache as any);
179183

180184
expect(fakeImpressionsCache.track).not.toBeCalled(); // cache method should not be called by just creating a tracker
181185

@@ -198,7 +202,7 @@ describe('Impressions Tracker', () => {
198202
test('Should track or not impressions depending on user consent status', () => {
199203
const settings = { ...fullSettings };
200204

201-
const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit);
205+
const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, fakeDebugStrategy, strategy, fakeWhenInit);
202206

203207
tracker.track([{ imp: impression }]);
204208
expect(fakeImpressionsCache.track).toBeCalledTimes(1); // impression should be tracked if userConsent is undefined

0 commit comments

Comments
 (0)