Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Impressions toggle] #376

Draft
wants to merge 23 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e6d46fd
Add 'track' property in ImpressionDTO and 'trackImpressions' property…
EmilianoSanchez Dec 6, 2024
b4c0350
Refactor storages and submitter, now that 'impressionCounts' and 'uni…
EmilianoSanchez Dec 9, 2024
c2f1e62
uniqueKeysTracker required
EmilianoSanchez Dec 9, 2024
77f8dee
Refactor strategies and impressionsTracker
EmilianoSanchez Dec 9, 2024
5eb34bf
Add trackImpressions property to SplitView
EmilianoSanchez Dec 10, 2024
f9dc947
Remove 'track' property from ImpressionDTO, to not modify the Impress…
EmilianoSanchez Dec 11, 2024
6b36f0d
Merge branch 'impressions_toggle_track_property' into impressions_tog…
EmilianoSanchez Dec 11, 2024
334ec45
Merge branch 'impressions_toggle_storage_refactors' into impressions_…
EmilianoSanchez Dec 11, 2024
2b1a189
Merge branch 'impressions_toggle_strategy_refactors' into impressions…
EmilianoSanchez Dec 11, 2024
c16be4a
rc
EmilianoSanchez Dec 11, 2024
ac5659a
Polishing
EmilianoSanchez Dec 11, 2024
522debd
New type ImpressionDecorated
EmilianoSanchez Dec 11, 2024
a771764
Merge branch 'impressions_toggle_track_property' into impressions_tog…
EmilianoSanchez Dec 11, 2024
f1b3a1f
Merge branch 'impressions_toggle_storage_refactors' into impressions_…
EmilianoSanchez Dec 11, 2024
84238a2
Merge branch 'impressions_toggle_strategy_refactors' into impressions…
EmilianoSanchez Dec 11, 2024
e462bba
Merge pull request #372 from splitio/impressions_toggle_track_property
EmilianoSanchez Dec 12, 2024
5255f06
Merge pull request #373 from splitio/impressions_toggle_storage_refac…
EmilianoSanchez Dec 12, 2024
7e13306
Update test
EmilianoSanchez Dec 12, 2024
e14acff
Merge pull request #374 from splitio/impressions_toggle_strategy_refa…
EmilianoSanchez Dec 12, 2024
0d3b71b
Merge branch 'development' into impressions_toggle_baseline
EmilianoSanchez Dec 12, 2024
045e38b
Merge branch 'impressions_toggle_baseline' into impressions_toggle_ma…
EmilianoSanchez Dec 12, 2024
d83c0ad
Merge pull request #375 from splitio/impressions_toggle_manager
EmilianoSanchez Dec 26, 2024
58040dc
Merge branch 'development' into impressions_toggle_baseline
EmilianoSanchez Dec 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
2.0.3 (December 29, 2024)
2.1.0 (January XX, 2025)
- Added `trackImpressions` property to SDK Manager's `SplitView` type.
- Updated implementation of the impressions tracker and strategies to support feature flags with impressions tracking disabled.
- Bugfixing - Properly handle rejected promises when using targeting rules with segment matchers in consumer modes (e.g., Redis and Pluggable storages).

2.0.2 (December 3, 2024)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-commons",
"version": "2.0.2",
"version": "2.0.3-rc.0",
"description": "Split JavaScript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
3 changes: 2 additions & 1 deletion src/dtos/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ export interface ISplit {
configurations?: {
[treatmentName: string]: string
},
sets?: string[]
sets?: string[],
trackImpressions?: boolean
}

// Split definition used in offline mode
Expand Down
2 changes: 2 additions & 0 deletions src/evaluator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,14 @@ function getEvaluation(
return evaluation.then(result => {
result.changeNumber = split.getChangeNumber();
result.config = splitJSON.configurations && splitJSON.configurations[result.treatment] || null;
result.track = splitJSON.trackImpressions;

return result;
});
} else {
evaluation.changeNumber = split.getChangeNumber(); // Always sync and optional
evaluation.config = splitJSON.configurations && splitJSON.configurations[evaluation.treatment] || null;
evaluation.track = splitJSON.trackImpressions;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/evaluator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface IEvaluation {
config?: string | null
}

export type IEvaluationResult = IEvaluation & { treatment: string }
export type IEvaluationResult = IEvaluation & { treatment: string; track?: boolean }

export type ISplitEvaluator = (log: ILogger, key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes | undefined, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>

Expand Down
4 changes: 1 addition & 3 deletions src/listeners/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { IResponse, ISplitApi } from '../services/types';
import { ISettings } from '../types';
import SplitIO from '../../types/splitio';
import { ImpressionsPayload } from '../sync/submitters/types';
import { OPTIMIZED, DEBUG, NONE } from '../utils/constants';
import { objectAssign } from '../utils/lang/objectAssign';
import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
import { ISyncManager } from '../sync/types';
Expand Down Expand Up @@ -78,10 +77,9 @@ export class BrowserSignalListener implements ISignalListener {

// Flush impressions & events data if there is user consent
if (isConsentGranted(this.settings)) {
const sim = this.settings.sync.impressionsMode;
const extraMetadata = {
// sim stands for Sync/Split Impressions Mode
sim: sim === OPTIMIZED ? OPTIMIZED : sim === DEBUG ? DEBUG : NONE
sim: this.settings.sync.impressionsMode
};

this._flushData(events + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
Expand Down
7 changes: 5 additions & 2 deletions src/sdkClient/__tests__/sdkClientMethod.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const paramMocks = [
signalListener: undefined,
settings: { mode: CONSUMER_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} },
telemetryTracker: telemetryTrackerFactory(),
clients: {}
clients: {},
uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() }
},
// SyncManager (i.e., Sync SDK) and Signal listener
{
Expand All @@ -26,7 +27,8 @@ const paramMocks = [
signalListener: { stop: jest.fn() },
settings: { mode: STANDALONE_MODE, log: loggerMock, core: { authorizationKey: 'sdk key '} },
telemetryTracker: telemetryTrackerFactory(),
clients: {}
clients: {},
uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() }
}
];

Expand Down Expand Up @@ -70,6 +72,7 @@ test.each(paramMocks)('sdkClientMethodFactory', (params, done: any) => {
client.destroy().then(() => {
expect(params.sdkReadinessManager.readinessManager.destroy).toBeCalledTimes(1);
expect(params.storage.destroy).toBeCalledTimes(1);
expect(params.uniqueKeysTracker.stop).toBeCalledTimes(1);

if (params.syncManager) {
expect(params.syncManager.stop).toBeCalledTimes(1);
Expand Down
2 changes: 2 additions & 0 deletions src/sdkClient/__tests__/sdkClientMethodCS.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const params = {
settings: settingsWithKey,
telemetryTracker: telemetryTrackerFactory(),
clients: {},
uniqueKeysTracker: { start: jest.fn(), stop: jest.fn() }
};

const invalidAttributes = [
Expand Down Expand Up @@ -95,6 +96,7 @@ describe('sdkClientMethodCSFactory', () => {
expect(params.syncManager.stop).toBeCalledTimes(1);
expect(params.syncManager.flush).toBeCalledTimes(1);
expect(params.signalListener.stop).toBeCalledTimes(1);
expect(params.uniqueKeysTracker.stop).toBeCalledTimes(1);
});

});
Expand Down
34 changes: 19 additions & 15 deletions src/sdkClient/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';
import { ISdkFactoryContext } from '../sdkFactory/types';
import { isConsumerMode } from '../utils/settingsValidation/mode';
import { Method } from '../sync/submitters/types';
import { ImpressionDecorated } from '../trackers/types';

const treatmentNotReady = { treatment: CONTROL, label: SDK_NOT_READY };

Expand All @@ -34,11 +35,11 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENT_WITH_CONFIG : TREATMENT);

const wrapUp = (evaluationResult: IEvaluationResult) => {
const queue: SplitIO.ImpressionDTO[] = [];
const queue: ImpressionDecorated[] = [];
const treatment = processEvaluation(evaluationResult, featureFlagName, key, attributes, withConfig, methodName, queue);
impressionsTracker.track(queue, attributes);

stopTelemetryTracker(queue[0] && queue[0].label);
stopTelemetryTracker(queue[0] && queue[0].imp.label);
return treatment;
};

Expand All @@ -59,14 +60,14 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
const stopTelemetryTracker = telemetryTracker.trackEval(withConfig ? TREATMENTS_WITH_CONFIG : TREATMENTS);

const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
const queue: SplitIO.ImpressionDTO[] = [];
const queue: ImpressionDecorated[] = [];
const treatments: Record<string, SplitIO.Treatment | SplitIO.TreatmentWithConfig> = {};
Object.keys(evaluationResults).forEach(featureFlagName => {
treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue);
});
impressionsTracker.track(queue, attributes);

stopTelemetryTracker(queue[0] && queue[0].label);
stopTelemetryTracker(queue[0] && queue[0].imp.label);
return treatments;
};

Expand All @@ -87,15 +88,15 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
const stopTelemetryTracker = telemetryTracker.trackEval(method);

const wrapUp = (evaluationResults: Record<string, IEvaluationResult>) => {
const queue: SplitIO.ImpressionDTO[] = [];
const queue: ImpressionDecorated[] = [];
const treatments: Record<string, SplitIO.Treatment | SplitIO.TreatmentWithConfig> = {};
const evaluations = evaluationResults;
Object.keys(evaluations).forEach(featureFlagName => {
treatments[featureFlagName] = processEvaluation(evaluations[featureFlagName], featureFlagName, key, attributes, withConfig, methodName, queue);
});
impressionsTracker.track(queue, attributes);

stopTelemetryTracker(queue[0] && queue[0].label);
stopTelemetryTracker(queue[0] && queue[0].imp.label);
return treatments;
};

Expand Down Expand Up @@ -128,24 +129,27 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl
attributes: SplitIO.Attributes | undefined,
withConfig: boolean,
invokingMethodName: string,
queue: SplitIO.ImpressionDTO[]
queue: ImpressionDecorated[]
): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
const matchingKey = getMatching(key);
const bucketingKey = getBucketing(key);

const { treatment, label, changeNumber, config = null } = evaluation;
const { treatment, label, changeNumber, config = null, track } = evaluation;
log.info(IMPRESSION, [featureFlagName, matchingKey, treatment, label]);

if (validateSplitExistence(log, readinessManager, featureFlagName, label, invokingMethodName)) {
log.info(IMPRESSION_QUEUEING);
queue.push({
feature: featureFlagName,
keyName: matchingKey,
treatment,
time: Date.now(),
bucketingKey,
label,
changeNumber: changeNumber as number
imp: {
feature: featureFlagName,
keyName: matchingKey,
treatment,
time: Date.now(),
bucketingKey,
label,
changeNumber: changeNumber as number,
},
track
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/sdkClient/sdkClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
releaseApiKey(settings.core.authorizationKey);
telemetryTracker.sessionLength();
signalListener && signalListener.stop();
uniqueKeysTracker && uniqueKeysTracker.stop();
uniqueKeysTracker.stop();
}

// Stop background jobs
Expand Down
27 changes: 11 additions & 16 deletions src/sdkFactory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
import { strategyNoneFactory } from '../trackers/strategy/strategyNone';
import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
import { NONE, OPTIMIZED } from '../utils/constants';
import { DEBUG, OPTIMIZED } from '../utils/constants';

/**
* Modular SDK factory
Expand Down Expand Up @@ -59,21 +59,16 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });

const observer = impressionsObserverFactory();
const uniqueKeysTracker = impressionsMode === NONE ? uniqueKeysTrackerFactory(log, storage.uniqueKeys!, filterAdapterFactory && filterAdapterFactory()) : undefined;

let strategy;
switch (impressionsMode) {
case OPTIMIZED:
strategy = strategyOptimizedFactory(observer, storage.impressionCounts!);
break;
case NONE:
strategy = strategyNoneFactory(storage.impressionCounts!, uniqueKeysTracker!);
break;
default:
strategy = strategyDebugFactory(observer);
}
const uniqueKeysTracker = uniqueKeysTrackerFactory(log, storage.uniqueKeys, filterAdapterFactory && filterAdapterFactory());

const noneStrategy = strategyNoneFactory(storage.impressionCounts, uniqueKeysTracker);
const strategy = impressionsMode === OPTIMIZED ?
strategyOptimizedFactory(observer, storage.impressionCounts) :
impressionsMode === DEBUG ?
strategyDebugFactory(observer) :
noneStrategy;

const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, strategy, 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
Expand All @@ -99,7 +94,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
// We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
validateAndTrackApiKey(log, settings.core.authorizationKey);
readiness.init();
uniqueKeysTracker && uniqueKeysTracker.start();
uniqueKeysTracker.start();
syncManager && syncManager.start();
signalListener && signalListener.start();

Expand Down
2 changes: 1 addition & 1 deletion src/sdkFactory/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface ISdkFactoryContext {
eventTracker: IEventTracker,
telemetryTracker: ITelemetryTracker,
storage: IStorageSync | IStorageAsync,
uniqueKeysTracker?: IUniqueKeysTracker,
uniqueKeysTracker: IUniqueKeysTracker,
signalListener?: ISignalListener
splitApi?: ISplitApi
syncManager?: ISyncManager,
Expand Down
3 changes: 2 additions & 1 deletion src/sdkManager/__tests__/mocks/output.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
"on": "\"color\": \"green\""
},
"sets": ["set_a"],
"defaultTreatment": "off"
"defaultTreatment": "off",
"trackImpressions": true
}
3 changes: 2 additions & 1 deletion src/sdkManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ function objectToView(splitObject: ISplit | null): SplitIO.SplitView | null {
treatments: collectTreatments(splitObject),
configs: splitObject.configurations || {},
sets: splitObject.sets || [],
defaultTreatment: splitObject.defaultTreatment
defaultTreatment: splitObject.defaultTreatment,
trackImpressions: splitObject.trackImpressions !== false
};
}

Expand Down
9 changes: 5 additions & 4 deletions src/storages/inLocalStorage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
import { LOG_PREFIX } from './constants';
import { DEBUG, NONE, STORAGE_LOCALSTORAGE } from '../../utils/constants';
import { STORAGE_LOCALSTORAGE } from '../../utils/constants';
import { shouldRecordTelemetry, TelemetryCacheInMemory } from '../inMemory/TelemetryCacheInMemory';
import { UniqueKeysCacheInMemoryCS } from '../inMemory/UniqueKeysCacheInMemoryCS';
import { getMatching } from '../../utils/key';
Expand All @@ -34,7 +34,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
return InMemoryStorageCSFactory(params);
}

const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize } } } = params;
const matchingKey = getMatching(settings.core.key);
const keys = new KeyBuilderCS(prefix, matchingKey);
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
Expand All @@ -48,10 +48,10 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
segments,
largeSegments,
impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
impressionCounts: new ImpressionCountsCacheInMemory(),
events: new EventsCacheInMemory(eventsQueueSize),
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,
uniqueKeys: new UniqueKeysCacheInMemoryCS(),

destroy() { },

Expand All @@ -66,6 +66,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
impressionCounts: this.impressionCounts,
events: this.events,
telemetry: this.telemetry,
uniqueKeys: this.uniqueKeys,

destroy() { }
};
Expand Down
12 changes: 6 additions & 6 deletions src/storages/inMemory/InMemoryStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ImpressionsCacheInMemory } from './ImpressionsCacheInMemory';
import { EventsCacheInMemory } from './EventsCacheInMemory';
import { IStorageFactoryParams, IStorageSync } from '../types';
import { ImpressionCountsCacheInMemory } from './ImpressionCountsCacheInMemory';
import { DEBUG, LOCALHOST_MODE, NONE, STORAGE_MEMORY } from '../../utils/constants';
import { LOCALHOST_MODE, STORAGE_MEMORY } from '../../utils/constants';
import { shouldRecordTelemetry, TelemetryCacheInMemory } from './TelemetryCacheInMemory';
import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';

Expand All @@ -14,7 +14,7 @@ import { UniqueKeysCacheInMemory } from './UniqueKeysCacheInMemory';
* @param params - parameters required by EventsCacheSync
*/
export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageSync {
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
const { settings: { scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { __splitFiltersValidation } } } = params;

const splits = new SplitsCacheInMemory(__splitFiltersValidation);
const segments = new SegmentsCacheInMemory();
Expand All @@ -23,10 +23,10 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
splits,
segments,
impressions: new ImpressionsCacheInMemory(impressionsQueueSize),
impressionCounts: impressionsMode !== DEBUG ? new ImpressionCountsCacheInMemory() : undefined,
impressionCounts: new ImpressionCountsCacheInMemory(),
events: new EventsCacheInMemory(eventsQueueSize),
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,
uniqueKeys: new UniqueKeysCacheInMemory(),

destroy() { }
};
Expand All @@ -37,8 +37,8 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
const noopTrack = () => true;
storage.impressions.track = noopTrack;
storage.events.track = noopTrack;
if (storage.impressionCounts) storage.impressionCounts.track = noopTrack;
if (storage.uniqueKeys) storage.uniqueKeys.track = noopTrack;
storage.impressionCounts.track = noopTrack;
storage.uniqueKeys.track = noopTrack;
}

return storage;
Expand Down
Loading
Loading