From 973aa550d26baee352ce22d3356a3429493f3952 Mon Sep 17 00:00:00 2001 From: Mark Doliner Date: Fri, 3 Jun 2022 15:25:22 -0400 Subject: [PATCH 01/11] Fix spelling of dropping. --- src/logger/messages/warn.ts | 2 +- src/storages/inRedis/__tests__/EventsCacheInRedis.spec.ts | 2 +- src/storages/inRedis/__tests__/ImpressionsCacheInRedis.spec.ts | 2 +- src/storages/pluggable/__tests__/EventsCachePluggable.spec.ts | 2 +- .../pluggable/__tests__/ImpressionsCachePluggable.spec.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index d820413d..3b3f984e 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -11,7 +11,7 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.STREAMING_PARSING_ERROR_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE error notification: %s'], [c.STREAMING_PARSING_MESSAGE_FAILS, c.LOG_PREFIX_SYNC_STREAMING + 'Error parsing SSE message notification: %s'], [c.STREAMING_FALLBACK, c.LOG_PREFIX_SYNC_STREAMING + 'Falling back to polling mode. Reason: %s'], - [c.SUBMITTERS_PUSH_FAILS, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Droping %s after retry. Reason: %s.'], + [c.SUBMITTERS_PUSH_FAILS, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Dropping %s after retry. Reason: %s.'], [c.SUBMITTERS_PUSH_RETRY, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Failed to push %s, keeping data to retry on next iteration. Reason: %s.'], // client status [c.CLIENT_NOT_READY, '%s: the SDK is not ready, results may be incorrect. Make sure to wait for SDK readiness before using this method.'], diff --git a/src/storages/inRedis/__tests__/EventsCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/EventsCacheInRedis.spec.ts index 9d648feb..e9d1ddd3 100644 --- a/src/storages/inRedis/__tests__/EventsCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/EventsCacheInRedis.spec.ts @@ -50,7 +50,7 @@ test('EVENTS CACHE IN REDIS / `track`, `count`, `popNWithMetadata` and `drop` me await Promise.all([cache.track(fakeEvent1), cache.track(fakeEvent2), cache.track(fakeEvent3)]); expect(await cache.count()).toBe(3); await cache.drop(); - expect(await cache.count()).toBe(0); // storage should be empty after droping it + expect(await cache.count()).toBe(0); // storage should be empty after dropping it // Clean up then end. await connection.del(eventsKey, nonListKey); diff --git a/src/storages/inRedis/__tests__/ImpressionsCacheInRedis.spec.ts b/src/storages/inRedis/__tests__/ImpressionsCacheInRedis.spec.ts index 8dcd1398..27460b34 100644 --- a/src/storages/inRedis/__tests__/ImpressionsCacheInRedis.spec.ts +++ b/src/storages/inRedis/__tests__/ImpressionsCacheInRedis.spec.ts @@ -41,7 +41,7 @@ describe('IMPRESSIONS CACHE IN REDIS', () => { await c.track([o1, o2, o3]); expect(await c.count()).toBe(3); await c.drop(); - expect(await c.count()).toBe(0); // storage should be empty after droping it + expect(await c.count()).toBe(0); // storage should be empty after dropping it await connection.del(impressionsKey); await connection.quit(); diff --git a/src/storages/pluggable/__tests__/EventsCachePluggable.spec.ts b/src/storages/pluggable/__tests__/EventsCachePluggable.spec.ts index 09d8c84a..7d44164d 100644 --- a/src/storages/pluggable/__tests__/EventsCachePluggable.spec.ts +++ b/src/storages/pluggable/__tests__/EventsCachePluggable.spec.ts @@ -47,7 +47,7 @@ describe('PLUGGABLE EVENTS CACHE', () => { await Promise.all([cache.track(fakeEvent1), cache.track(fakeEvent2), cache.track(fakeEvent3)]); expect(await cache.count()).toBe(3); await cache.drop(); - expect(await cache.count()).toBe(0); // storage should be empty after droping it + expect(await cache.count()).toBe(0); // storage should be empty after dropping it wrapperMock.mockClear(); }); diff --git a/src/storages/pluggable/__tests__/ImpressionsCachePluggable.spec.ts b/src/storages/pluggable/__tests__/ImpressionsCachePluggable.spec.ts index d978be1b..4f1c794e 100644 --- a/src/storages/pluggable/__tests__/ImpressionsCachePluggable.spec.ts +++ b/src/storages/pluggable/__tests__/ImpressionsCachePluggable.spec.ts @@ -86,7 +86,7 @@ describe('PLUGGABLE IMPRESSIONS CACHE', () => { await cache.track([o1, o2, o3]); expect(await cache.count()).toBe(3); await cache.drop(); - expect(await cache.count()).toBe(0); // storage should be empty after droping it + expect(await cache.count()).toBe(0); // storage should be empty after dropping it }); From c29b42712d5e0d1cbfb7a9144952e519c7c0b189 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 10 Jun 2022 11:51:41 -0300 Subject: [PATCH 02/11] update cache method names and fix submitter issue dropping impressions --- package-lock.json | 6 +++--- src/listeners/__tests__/browser.spec.ts | 8 ++++---- src/listeners/browser.ts | 2 +- src/storages/__tests__/testUtils.ts | 2 +- src/storages/inMemory/EventsCacheInMemory.ts | 8 +++++--- .../inMemory/ImpressionCountsCacheInMemory.ts | 18 +++++++++++++---- .../inMemory/ImpressionsCacheInMemory.ts | 8 +++++--- .../__tests__/EventsCacheInMemory.spec.ts | 20 ++++++++++--------- .../ImpressionCountsCacheInMemory.spec.ts | 11 +++++----- .../ImpressionsCacheInMemory.spec.ts | 8 +++++--- src/storages/types.ts | 7 +++---- src/sync/submitters/submitter.ts | 13 +++++++----- src/sync/submitters/telemetrySubmitter.ts | 4 ++-- .../__tests__/impressionsTracker.spec.ts | 2 +- 14 files changed, 69 insertions(+), 48 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2be921ba..c69382e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4836,19 +4836,19 @@ "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", "dev": true }, "lodash.isequal": { diff --git a/src/listeners/__tests__/browser.spec.ts b/src/listeners/__tests__/browser.spec.ts index 4657d24e..566d6a68 100644 --- a/src/listeners/__tests__/browser.spec.ts +++ b/src/listeners/__tests__/browser.spec.ts @@ -9,7 +9,7 @@ jest.mock('../../sync/submitters/telemetrySubmitter', () => { return { isEmpty: () => false, clear: () => { }, - state: () => ({}), + pop: () => ({}), }; } }; @@ -43,21 +43,21 @@ const fakeStorageOptimized = { // @ts-expect-error impressions: { isEmpty: jest.fn(), clear: jest.fn(), - state() { + pop() { return [fakeImpression]; } } as IImpressionsCacheSync, // @ts-expect-error events: { isEmpty: jest.fn(), clear: jest.fn(), - state() { + pop() { return [fakeEvent]; } } as IEventsCacheSync, // @ts-expect-error impressionCounts: { isEmpty: jest.fn(), clear: jest.fn(), - state() { + pop() { return fakeImpressionCounts; } } as IImpressionCountsCacheSync, diff --git a/src/listeners/browser.ts b/src/listeners/browser.ts index faf5c956..d5e3d2a1 100644 --- a/src/listeners/browser.ts +++ b/src/listeners/browser.ts @@ -92,7 +92,7 @@ export class BrowserSignalListener implements ISignalListener { private _flushData(url: string, cache: IRecorderCacheProducerSync, postService: (body: string) => Promise, fromCacheToPayload?: (cacheData: TState) => any, extraMetadata?: {}) { // if there is data in cache, send it to backend if (!cache.isEmpty()) { - const dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.state()) : cache.state(); + const dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.pop()) : cache.pop(); if (!this._sendBeacon(url, dataPayload, extraMetadata)) { postService(JSON.stringify(dataPayload)).catch(() => { }); // no-op just to catch a possible exception } diff --git a/src/storages/__tests__/testUtils.ts b/src/storages/__tests__/testUtils.ts index 520dba1d..e2724a3f 100644 --- a/src/storages/__tests__/testUtils.ts +++ b/src/storages/__tests__/testUtils.ts @@ -14,7 +14,7 @@ export function assertStorageInterface(storage: IStorageSync | IStorageAsync) { export function assertSyncRecorderCacheInterface(cache: IEventsCacheSync | IImpressionsCacheSync) { expect(typeof cache.isEmpty).toBe('function'); expect(typeof cache.clear).toBe('function'); - expect(typeof cache.state).toBe('function'); + expect(typeof cache.pop).toBe('function'); expect(typeof cache.track).toBe('function'); } diff --git a/src/storages/inMemory/EventsCacheInMemory.ts b/src/storages/inMemory/EventsCacheInMemory.ts index d209cba6..8c6b9cc7 100644 --- a/src/storages/inMemory/EventsCacheInMemory.ts +++ b/src/storages/inMemory/EventsCacheInMemory.ts @@ -46,10 +46,12 @@ export class EventsCacheInMemory implements IEventsCacheSync { } /** - * Get the collected data, used as payload for posting. + * Pop the collected data, used as payload for posting. */ - state() { - return this.queue; + pop() { + const data = this.queue; + this.clear(); + return data; } /** diff --git a/src/storages/inMemory/ImpressionCountsCacheInMemory.ts b/src/storages/inMemory/ImpressionCountsCacheInMemory.ts index f7598d2a..8c4d958e 100644 --- a/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +++ b/src/storages/inMemory/ImpressionCountsCacheInMemory.ts @@ -20,17 +20,27 @@ export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync this.cache[key] = currentAmount ? currentAmount + amount : amount; } + + /** - * Returns all the elements stored in the cache and resets the cache. - */ - state() { - return this.cache; + * Pop the collected data, used as payload for posting. + */ + pop() { + const data = this.cache; + this.clear(); + return data; } + /** + * Clear the data stored on the cache. + */ clear() { this.cache = {}; } + /** + * Check if the cache is empty. + */ isEmpty() { return Object.keys(this.cache).length === 0; } diff --git a/src/storages/inMemory/ImpressionsCacheInMemory.ts b/src/storages/inMemory/ImpressionsCacheInMemory.ts index 57621c71..29bdac73 100644 --- a/src/storages/inMemory/ImpressionsCacheInMemory.ts +++ b/src/storages/inMemory/ImpressionsCacheInMemory.ts @@ -41,10 +41,12 @@ export class ImpressionsCacheInMemory implements IImpressionsCacheSync { } /** - * Get the collected data, used as payload for posting. + * Pop the collected data, used as payload for posting. */ - state() { - return this.queue; + pop() { + const data = this.queue; + this.clear(); + return data; } /** diff --git a/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts index 37a6a4ad..fa5e4d11 100644 --- a/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts @@ -6,7 +6,7 @@ test('EVENTS CACHE / Should be able to instantiate and start with an empty queue const createInstance = () => cache = new EventsCacheInMemory(500); // 500 as eventsQueueSize expect(createInstance).not.toThrow(); // Creation should not throw. - expect(cache.state()).toEqual([]); // The queue starts empty. + expect(cache.pop()).toEqual([]); // The queue starts empty. }); test('EVENTS CACHE / Should be able to add items sequentially and retrieve the queue', () => { @@ -19,10 +19,11 @@ test('EVENTS CACHE / Should be able to add items sequentially and retrieve the q cache.track(queueValues[2]); cache.track(queueValues[3]); - const state = cache.state(); + const items = cache.pop(); - expect(state.length).toBe(4 /* pushed 4 items */); // The amount of items on queue should match the amount we pushed - expect(state).toEqual(queueValues); // The items should be in the queue and ordered as they were added. + expect(items.length).toBe(4 /* pushed 4 items */); // The amount of items on queue should match the amount we pushed + expect(items).toEqual(queueValues); // The items should be in the queue and ordered as they were added. + expect(cache.isEmpty()).toEqual(true); // Queue is empty }); test('EVENTS CACHE / Should be able to clear the queue and accumulated byte size', () => { @@ -31,20 +32,21 @@ test('EVENTS CACHE / Should be able to clear the queue and accumulated byte size cache.track('test1', 2019); cache.clear(); - expect(cache.state()).toEqual([]); // The queue should be clear. + expect(cache.pop()).toEqual([]); // The queue should be clear. expect(cache.queueByteSize).toBe(0); // The accumulated byte size should had been cleared. }); test('EVENTS CACHE / Should be able to tell if the queue is empty', () => { const cache = new EventsCacheInMemory(500); - expect(cache.state().length === 0).toBe(true); // The queue is empty, + expect(cache.pop().length === 0).toBe(true); // The queue is empty, expect(cache.isEmpty()).toBe(true); // so if it is empty, it returns true. cache.track('test'); - - expect(cache.state().length > 0).toBe(true); // If we add something to the queue, expect(cache.isEmpty()).toBe(false); // it will return false. + + expect(cache.pop().length > 0).toBe(true); // If we add something to the queue, + expect(cache.isEmpty()).toBe(true); // it will return true. }); test('EVENTS CACHE / Should be able to return the DTO we will send to BE', () => { @@ -56,7 +58,7 @@ test('EVENTS CACHE / Should be able to return the DTO we will send to BE', () => cache.track(queueValues[2]); cache.track(queueValues[3]); - const json = cache.state(); + const json = cache.pop(); expect(json).toEqual(queueValues); // For now the DTO is just an array of the saved events. }); diff --git a/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts index 00d878c9..1aa140be 100644 --- a/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts @@ -20,12 +20,12 @@ test('IMPRESSION COUNTS CACHE / Impression Counter Test BasicUsage', () => { counter.track('feature2', timestamp + 3, 2); counter.track('feature2', timestamp + 4, 2); - const counted = counter.state(); + const counted = counter.pop(); expect(Object.keys(counted).length).toBe(2); expect(counted[counter._makeKey('feature1', timestamp)]).toBe(3); expect(counted[counter._makeKey('feature2', timestamp)]).toBe(4); counter.clear(); - expect(Object.keys(counter.state()).length).toBe(0); + expect(Object.keys(counter.pop()).length).toBe(0); const nextHourTimestamp = new Date(2020, 9, 2, 11, 10, 12).getTime(); counter.track('feature1', timestamp, 1); @@ -38,12 +38,13 @@ test('IMPRESSION COUNTS CACHE / Impression Counter Test BasicUsage', () => { counter.track('feature1', nextHourTimestamp + 2, 1); counter.track('feature2', nextHourTimestamp + 3, 2); counter.track('feature2', nextHourTimestamp + 4, 2); - const counted2 = counter.state(); + expect(counter.isEmpty()).toBe(false); + const counted2 = counter.pop(); + expect(counter.isEmpty()).toBe(true); expect(Object.keys(counted2).length).toBe(4); expect(counted2[counter._makeKey('feature1', timestamp)]).toBe(3); expect(counted2[counter._makeKey('feature2', timestamp)]).toBe(4); expect(counted2[counter._makeKey('feature1', nextHourTimestamp)]).toBe(3); expect(counted2[counter._makeKey('feature2', nextHourTimestamp)]).toBe(4); - counter.clear(); - expect(Object.keys(counter.state()).length).toBe(0); + expect(Object.keys(counter.pop()).length).toBe(0); }); diff --git a/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts index 4a50eaf8..b4b1df2b 100644 --- a/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts @@ -5,7 +5,7 @@ test('IMPRESSIONS CACHE IN MEMORY / should incrementally store values, clear the const c = new ImpressionsCacheInMemory(); // queue is initially empty - expect(c.state()).toEqual([]); + expect(c.pop()).toEqual([]); expect(c.isEmpty()).toBe(true); @@ -13,12 +13,14 @@ test('IMPRESSIONS CACHE IN MEMORY / should incrementally store values, clear the c.track([1, 2]); c.track([3]); - expect(c.state()).toEqual([0, 1, 2, 3]); // all the items should be stored in sequential order expect(c.isEmpty()).toBe(false); + expect(c.pop()).toEqual([0, 1, 2, 3]); // all the items should be stored in sequential order + expect(c.isEmpty()).toBe(true); // should empty the queue + c.track([0]); c.clear(); - expect(c.state()).toEqual([]); + expect(c.pop()).toEqual([]); expect(c.isEmpty()).toBe(true); }); diff --git a/src/storages/types.ts b/src/storages/types.ts index ea8616fa..6ff47faf 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -301,8 +301,8 @@ export interface IRecorderCacheProducerSync { isEmpty(): boolean /* Clears cache data */ clear(): void - /* Gets cache data */ - state(): T + /* Pops cache data */ + pop(): T } @@ -352,8 +352,7 @@ export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync // get cache data + pop(): Record // pop cache data } diff --git a/src/sync/submitters/submitter.ts b/src/sync/submitters/submitter.ts index a1312c7d..67f7d4f4 100644 --- a/src/sync/submitters/submitter.ts +++ b/src/sync/submitters/submitter.ts @@ -20,27 +20,30 @@ export function submitterFactory( ): ISyncTask<[], void> { let retries = 0; + let data: TState | undefined; function postData(): Promise { - if (sourceCache.isEmpty()) return Promise.resolve(); + if (!data) { + if (sourceCache.isEmpty()) return Promise.resolve(); + // we clear the cache to track new items, while `data` is used for retries + data = sourceCache.pop(); + } - const data = sourceCache.state(); // @ts-ignore const dataCountMessage = typeof data.length === 'number' ? `${data.length} ${dataName}` : dataName; log[debugLogs ? 'debug' : 'info'](SUBMITTERS_PUSH, [dataCountMessage]); const jsonPayload = JSON.stringify(fromCacheToPayload ? fromCacheToPayload(data) : data); - if (!maxRetries) sourceCache.clear(); return postClient(jsonPayload).then(() => { retries = 0; - sourceCache.clear(); // we clear the queue if request successes. + data = undefined; }).catch(err => { if (!maxRetries) { log[debugLogs ? 'debug' : 'warn'](SUBMITTERS_PUSH_FAILS, [dataCountMessage, err]); } else if (retries === maxRetries) { retries = 0; - sourceCache.clear(); // we clear the queue if request fails after retries. + data = undefined; log[debugLogs ? 'debug' : 'warn'](SUBMITTERS_PUSH_FAILS, [dataCountMessage, err]); } else { retries++; diff --git a/src/sync/submitters/telemetrySubmitter.ts b/src/sync/submitters/telemetrySubmitter.ts index 8e208e59..e23f0d6c 100644 --- a/src/sync/submitters/telemetrySubmitter.ts +++ b/src/sync/submitters/telemetrySubmitter.ts @@ -19,7 +19,7 @@ export function telemetryCacheStatsAdapter(telemetry: ITelemetryCacheSync, split clear() { }, // No-op // @TODO consider moving inside telemetry cache for code size reduction - state(): TelemetryUsageStatsPayload { + pop(): TelemetryUsageStatsPayload { return { lS: telemetry.getLastSynchronization(), mL: telemetry.popLatencies(), @@ -88,7 +88,7 @@ export function telemetryCacheConfigAdapter(telemetry: ITelemetryCacheSync, sett isEmpty() { return false; }, clear() { }, - state(): TelemetryConfigStatsPayload { + pop(): TelemetryConfigStatsPayload { const { urls, scheduler } = settings; const isClientSide = settings.core.key !== undefined; diff --git a/src/trackers/__tests__/impressionsTracker.spec.ts b/src/trackers/__tests__/impressionsTracker.spec.ts index 5ca63895..ff0d8368 100644 --- a/src/trackers/__tests__/impressionsTracker.spec.ts +++ b/src/trackers/__tests__/impressionsTracker.spec.ts @@ -188,7 +188,7 @@ describe('Impressions Tracker', () => { expect(lastArgs[0][1].pt).toBe(undefined); expect(lastArgs[0][1].feature).toBe('qc_team_2'); - expect(Object.keys(impressionCountsCache.state()).length).toBe(2); + expect(Object.keys(impressionCountsCache.pop()).length).toBe(2); expect(fakeTelemetryCache.recordImpressionStats.mock.calls).toEqual([[QUEUED, 2], [DEDUPED, 1]]); }); From 1ef1bb8c50f87f754b707769ba0b4bac09fee53d Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 10 Jun 2022 14:58:28 -0300 Subject: [PATCH 03/11] unit tests --- CHANGES.txt | 3 ++ package-lock.json | 2 +- package.json | 2 +- .../__tests__/eventsSubmitter.spec.ts | 30 +++++++++++++++++-- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 00388146..3d681b60 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +1.4.1 (June 13, 2022) + - Bugfixing - Updated submitters logic, to avoid dropping impressions and events that are being tracked while POST request is pending. + 1.4.0 (May 24, 2022) - Added `scheduler.telemetryRefreshRate` property to SDK configuration, and deprecated `scheduler.metricsRefreshRate` property. - Updated SDK telemetry storage, metrics and updater to be more effective and send less often. diff --git a/package-lock.json b/package-lock.json index c69382e2..bbc43773 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.4.0", + "version": "1.4.1-rc.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 671e18f6..7d31831f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.4.0", + "version": "1.4.1-rc.0", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/sync/submitters/__tests__/eventsSubmitter.spec.ts b/src/sync/submitters/__tests__/eventsSubmitter.spec.ts index 16b26bc1..60de0379 100644 --- a/src/sync/submitters/__tests__/eventsSubmitter.spec.ts +++ b/src/sync/submitters/__tests__/eventsSubmitter.spec.ts @@ -1,6 +1,6 @@ import { eventsSubmitterFactory } from '../eventsSubmitter'; import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; - +import { EventsCacheInMemory } from '../../../storages/inMemory/EventsCacheInMemory'; describe('Events submitter', () => { @@ -16,7 +16,7 @@ describe('Events submitter', () => { scheduler: { eventsPushRate: 30000 }, startup: { eventsFirstPushWindow: 0 } }, - splitApi: { postEventsBulkMock: jest.fn() }, + splitApi: {}, storage: { events: eventsCacheMock } }; @@ -68,4 +68,30 @@ describe('Events submitter', () => { expect(eventsSubmitter.isRunning()).toEqual(false); }); + test('doesn\'t drop items from cache when POST is resolved', (done) => { + const eventsCacheInMemory = new EventsCacheInMemory(); + const params = { + settings: { log: loggerMock, scheduler: { eventsPushRate: 100 }, startup: { eventsFirstPushWindow: 0 } }, + storage: { events: eventsCacheInMemory }, + splitApi: { postEventsBulk: jest.fn(() => Promise.resolve()) }, + }; // @ts-ignore + const eventsSubmitter = eventsSubmitterFactory(params); + + eventsCacheInMemory.track({ eventTypeId: 'event1', timestamp: 1 }); + + eventsSubmitter.start(); + // Tracking event when POST is pending + eventsCacheInMemory.track({ eventTypeId: 'event2', timestamp: 1 }); + // Tracking event when POST is resolved + setTimeout(() => { eventsCacheInMemory.track({ eventTypeId: 'event3', timestamp: 1 }); }); + + setTimeout(() => { + expect(params.splitApi.postEventsBulk).toBeCalledTimes(2); + expect(params.splitApi.postEventsBulk.mock.calls).toEqual([['[{"eventTypeId":"event1","timestamp":1}]'], ['[{"eventTypeId":"event2","timestamp":1},{"eventTypeId":"event3","timestamp":1}]']]); + eventsSubmitter.stop(); + + done(); + }, params.settings.scheduler.eventsPushRate + 10); + }); + }); From e5bb6fdb9922591f076a37c7d5cf39cb372c088c Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 10 Jun 2022 16:18:17 -0300 Subject: [PATCH 04/11] fix for submitters without retries (telemetry, events) --- src/sync/submitters/submitter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sync/submitters/submitter.ts b/src/sync/submitters/submitter.ts index 67f7d4f4..6b3c6c09 100644 --- a/src/sync/submitters/submitter.ts +++ b/src/sync/submitters/submitter.ts @@ -34,6 +34,7 @@ export function submitterFactory( log[debugLogs ? 'debug' : 'info'](SUBMITTERS_PUSH, [dataCountMessage]); const jsonPayload = JSON.stringify(fromCacheToPayload ? fromCacheToPayload(data) : data); + if (!maxRetries) data = undefined; return postClient(jsonPayload).then(() => { retries = 0; From 4e5f7c2d52cdcb682d7676e1421c25316e9f339f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 10 Jun 2022 16:35:30 -0300 Subject: [PATCH 05/11] update rc version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bbc43773..892984bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.4.1-rc.0", + "version": "1.4.1-rc.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 7d31831f..d023d372 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.4.1-rc.0", + "version": "1.4.1-rc.1", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", From 6b80dedb6f8eb4b53f27d2dd75332809b402776e Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sun, 12 Jun 2022 20:53:16 -0300 Subject: [PATCH 06/11] update submitter to pop cache on retry --- src/listeners/browser.ts | 2 +- .../__tests__/impressionsSubmitter.spec.ts | 75 +++++++++++++++++++ src/sync/submitters/submitter.ts | 13 ++-- 3 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 src/sync/submitters/__tests__/impressionsSubmitter.spec.ts diff --git a/src/listeners/browser.ts b/src/listeners/browser.ts index d5e3d2a1..77574eba 100644 --- a/src/listeners/browser.ts +++ b/src/listeners/browser.ts @@ -89,7 +89,7 @@ export class BrowserSignalListener implements ISignalListener { if (this.syncManager.pushManager) this.syncManager.pushManager.stop(); } - private _flushData(url: string, cache: IRecorderCacheProducerSync, postService: (body: string) => Promise, fromCacheToPayload?: (cacheData: TState) => any, extraMetadata?: {}) { + private _flushData(url: string, cache: IRecorderCacheProducerSync, postService: (body: string) => Promise, fromCacheToPayload?: (cacheData: T) => any, extraMetadata?: {}) { // if there is data in cache, send it to backend if (!cache.isEmpty()) { const dataPayload = fromCacheToPayload ? fromCacheToPayload(cache.pop()) : cache.pop(); diff --git a/src/sync/submitters/__tests__/impressionsSubmitter.spec.ts b/src/sync/submitters/__tests__/impressionsSubmitter.spec.ts new file mode 100644 index 00000000..d5c74144 --- /dev/null +++ b/src/sync/submitters/__tests__/impressionsSubmitter.spec.ts @@ -0,0 +1,75 @@ +import { impressionsSubmitterFactory } from '../impressionsSubmitter'; +import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; +import { ImpressionsCacheInMemory } from '../../../storages/inMemory/ImpressionsCacheInMemory'; + +const imp1 = { + feature: 'someFeature', + keyName: 'k1', + changeNumber: 123, + label: 'someLabel', + treatment: 'someTreatment', + time: 0 +}; + +describe('Impressions submitter', () => { + + const impressionsCacheInMemory = new ImpressionsCacheInMemory(); + const params = { + settings: { log: loggerMock, scheduler: { impressionsPushRate: 100 }, core: {} }, + storage: { impressions: impressionsCacheInMemory }, + splitApi: { postTestImpressionsBulk: jest.fn(() => Promise.resolve()) }, + }; // @ts-ignore + const impressionsSubmitter = impressionsSubmitterFactory(params); + + beforeEach(() => { + params.splitApi.postTestImpressionsBulk.mockClear(); + }); + + test('doesn\'t drop items from cache when POST is resolved', (done) => { + + + impressionsCacheInMemory.track([imp1]); + impressionsSubmitter.start(); + + // Tracking impression when POST is pending + impressionsCacheInMemory.track([{ ...imp1, keyName: 'k2' }]); + // Tracking impression after POST is resolved + setTimeout(() => { impressionsCacheInMemory.track([{ ...imp1, keyName: 'k3', }]); }); + + setTimeout(() => { + expect(params.splitApi.postTestImpressionsBulk.mock.calls).toEqual([ + // impression for k1 + ['[{"f":"someFeature","i":[{"k":"k1","t":"someTreatment","m":0,"c":123}]}]'], + // impressions for k2 and k3 + ['[{"f":"someFeature","i":[{"k":"k2","t":"someTreatment","m":0,"c":123},{"k":"k3","t":"someTreatment","m":0,"c":123}]}]']]); + impressionsSubmitter.stop(); + + done(); + }, params.settings.scheduler.impressionsPushRate + 10); + }); + + test('in case of retry, pop new items from cache to include in the POST payload', (done) => { + // Make the POST request fail + params.splitApi.postTestImpressionsBulk.mockImplementation(() => Promise.reject()); + + impressionsCacheInMemory.track([imp1]); + impressionsSubmitter.start(); + + // Tracking impression when POST is pending + impressionsCacheInMemory.track([{ ...imp1, keyName: 'k2' }]); + // Tracking impression after POST is rejected + setTimeout(() => { impressionsCacheInMemory.track([{ ...imp1, keyName: 'k3', }]); }); + + setTimeout(() => { + expect(params.splitApi.postTestImpressionsBulk.mock.calls).toEqual([ + // impression for k1 + ['[{"f":"someFeature","i":[{"k":"k1","t":"someTreatment","m":0,"c":123}]}]'], + // impressions for k1, k2 and k3 + ['[{"f":"someFeature","i":[{"k":"k1","t":"someTreatment","m":0,"c":123},{"k":"k2","t":"someTreatment","m":0,"c":123},{"k":"k3","t":"someTreatment","m":0,"c":123}]}]']]); + impressionsSubmitter.stop(); + + done(); + }, params.settings.scheduler.impressionsPushRate + 10); + }); + +}); diff --git a/src/sync/submitters/submitter.ts b/src/sync/submitters/submitter.ts index 6b3c6c09..2472e072 100644 --- a/src/sync/submitters/submitter.ts +++ b/src/sync/submitters/submitter.ts @@ -8,28 +8,29 @@ import { IResponse } from '../../services/types'; /** * Base function to create submitters, such as ImpressionsSubmitter and EventsSubmitter */ -export function submitterFactory( +export function submitterFactory( log: ILogger, postClient: (body: string) => Promise, - sourceCache: IRecorderCacheProducerSync, + sourceCache: IRecorderCacheProducerSync, postRate: number, dataName: string, - fromCacheToPayload?: (cacheData: TState) => any, + fromCacheToPayload?: (cacheData: T) => any, maxRetries: number = 0, debugLogs?: boolean // true for telemetry submitters ): ISyncTask<[], void> { let retries = 0; - let data: TState | undefined; + let data: any; function postData(): Promise { - if (!data) { + if (data) { + if (Array.isArray(data) && !sourceCache.isEmpty()) data = data.concat(sourceCache.pop()); + } else { if (sourceCache.isEmpty()) return Promise.resolve(); // we clear the cache to track new items, while `data` is used for retries data = sourceCache.pop(); } - // @ts-ignore const dataCountMessage = typeof data.length === 'number' ? `${data.length} ${dataName}` : dataName; log[debugLogs ? 'debug' : 'info'](SUBMITTERS_PUSH, [dataCountMessage]); From c4fec5b9123448ef9bcc5dbc96d8ea7716b8796f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 13 Jun 2022 13:12:19 -0300 Subject: [PATCH 07/11] update rc version --- package-lock.json | 2 +- package.json | 2 +- src/sync/submitters/submitter.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 892984bc..5f3b792a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.4.1-rc.1", + "version": "1.4.1-rc.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d023d372..d7f18b67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.4.1-rc.1", + "version": "1.4.1-rc.2", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/sync/submitters/submitter.ts b/src/sync/submitters/submitter.ts index 2472e072..28e1489e 100644 --- a/src/sync/submitters/submitter.ts +++ b/src/sync/submitters/submitter.ts @@ -24,6 +24,7 @@ export function submitterFactory( function postData(): Promise { if (data) { + // if data is an array, append new items from cache if (Array.isArray(data) && !sourceCache.isEmpty()) data = data.concat(sourceCache.pop()); } else { if (sourceCache.isEmpty()) return Promise.resolve(); From 2c1fbb65493656fdd7c352d6c34430b7ea390f45 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 13 Jun 2022 14:36:29 -0300 Subject: [PATCH 08/11] refactor and merge payload for impressionCounts --- src/storages/inMemory/EventsCacheInMemory.ts | 4 ++-- .../inMemory/ImpressionCountsCacheInMemory.ts | 9 ++++++++- src/storages/inMemory/ImpressionsCacheInMemory.ts | 4 ++-- .../inMemory/__tests__/EventsCacheInMemory.spec.ts | 8 ++++++-- .../__tests__/ImpressionCountsCacheInMemory.spec.ts | 10 ++++++++++ .../__tests__/ImpressionsCacheInMemory.spec.ts | 5 ++++- src/storages/types.ts | 4 ++-- src/sync/submitters/submitter.ts | 11 +++-------- 8 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/storages/inMemory/EventsCacheInMemory.ts b/src/storages/inMemory/EventsCacheInMemory.ts index 8c6b9cc7..64525cdf 100644 --- a/src/storages/inMemory/EventsCacheInMemory.ts +++ b/src/storages/inMemory/EventsCacheInMemory.ts @@ -48,10 +48,10 @@ export class EventsCacheInMemory implements IEventsCacheSync { /** * Pop the collected data, used as payload for posting. */ - pop() { + pop(toMerge?: SplitIO.EventData[]) { const data = this.queue; this.clear(); - return data; + return toMerge ? toMerge.concat(data) : data; } /** diff --git a/src/storages/inMemory/ImpressionCountsCacheInMemory.ts b/src/storages/inMemory/ImpressionCountsCacheInMemory.ts index 8c4d958e..de32ab19 100644 --- a/src/storages/inMemory/ImpressionCountsCacheInMemory.ts +++ b/src/storages/inMemory/ImpressionCountsCacheInMemory.ts @@ -25,9 +25,16 @@ export class ImpressionCountsCacheInMemory implements IImpressionCountsCacheSync /** * Pop the collected data, used as payload for posting. */ - pop() { + pop(toMerge?: Record) { const data = this.cache; this.clear(); + if (toMerge) { + Object.keys(data).forEach((key) => { + if (toMerge[key]) toMerge[key] += data[key]; + else toMerge[key] = data[key]; + }); + return toMerge; + } return data; } diff --git a/src/storages/inMemory/ImpressionsCacheInMemory.ts b/src/storages/inMemory/ImpressionsCacheInMemory.ts index 29bdac73..a3d46634 100644 --- a/src/storages/inMemory/ImpressionsCacheInMemory.ts +++ b/src/storages/inMemory/ImpressionsCacheInMemory.ts @@ -43,10 +43,10 @@ export class ImpressionsCacheInMemory implements IImpressionsCacheSync { /** * Pop the collected data, used as payload for posting. */ - pop() { + pop(toMerge?: ImpressionDTO[]) { const data = this.queue; this.clear(); - return data; + return toMerge ? toMerge.concat(data) : data; } /** diff --git a/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts index fa5e4d11..293523b1 100644 --- a/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/EventsCacheInMemory.spec.ts @@ -39,13 +39,13 @@ test('EVENTS CACHE / Should be able to clear the queue and accumulated byte size test('EVENTS CACHE / Should be able to tell if the queue is empty', () => { const cache = new EventsCacheInMemory(500); - expect(cache.pop().length === 0).toBe(true); // The queue is empty, + expect(cache.pop().length).toBe(0); // The queue is empty, expect(cache.isEmpty()).toBe(true); // so if it is empty, it returns true. cache.track('test'); expect(cache.isEmpty()).toBe(false); // it will return false. - expect(cache.pop().length > 0).toBe(true); // If we add something to the queue, + expect(cache.pop().length).toBe(1); // If we add something to the queue, expect(cache.isEmpty()).toBe(true); // it will return true. }); @@ -60,6 +60,10 @@ test('EVENTS CACHE / Should be able to return the DTO we will send to BE', () => const json = cache.pop(); expect(json).toEqual(queueValues); // For now the DTO is just an array of the saved events. + + // pop with merge + cache.track(0); cache.track(1); + expect(cache.pop([2, 3, 4])).toEqual([2, 3, 4, 0, 1]); }); test('EVENTS CACHE / Should call "onFullQueueCb" when the queue is full (count wise).', () => { diff --git a/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts index 1aa140be..b853bb2f 100644 --- a/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/ImpressionCountsCacheInMemory.spec.ts @@ -24,6 +24,16 @@ test('IMPRESSION COUNTS CACHE / Impression Counter Test BasicUsage', () => { expect(Object.keys(counted).length).toBe(2); expect(counted[counter._makeKey('feature1', timestamp)]).toBe(3); expect(counted[counter._makeKey('feature2', timestamp)]).toBe(4); + + // pop with merge + counter.track('feature1', timestamp, 1); + counter.track('feature3', timestamp, 10); + const countedWithMerge = counter.pop(counted); + expect(Object.keys(countedWithMerge).length).toBe(3); + expect(countedWithMerge[counter._makeKey('feature1', timestamp)]).toBe(4); + expect(countedWithMerge[counter._makeKey('feature2', timestamp)]).toBe(4); + expect(countedWithMerge[counter._makeKey('feature3', timestamp)]).toBe(10); + counter.clear(); expect(Object.keys(counter.pop()).length).toBe(0); diff --git a/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts b/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts index b4b1df2b..f90c1309 100644 --- a/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts +++ b/src/storages/inMemory/__tests__/ImpressionsCacheInMemory.spec.ts @@ -8,7 +8,6 @@ test('IMPRESSIONS CACHE IN MEMORY / should incrementally store values, clear the expect(c.pop()).toEqual([]); expect(c.isEmpty()).toBe(true); - c.track([0]); c.track([1, 2]); c.track([3]); @@ -17,6 +16,10 @@ test('IMPRESSIONS CACHE IN MEMORY / should incrementally store values, clear the expect(c.pop()).toEqual([0, 1, 2, 3]); // all the items should be stored in sequential order expect(c.isEmpty()).toBe(true); + // pop with merge + c.track([0]); c.track([1]); + expect(c.pop([2, 3, 4])).toEqual([2, 3, 4, 0, 1]); + // should empty the queue c.track([0]); c.clear(); diff --git a/src/storages/types.ts b/src/storages/types.ts index 6ff47faf..1029ad62 100644 --- a/src/storages/types.ts +++ b/src/storages/types.ts @@ -302,7 +302,7 @@ export interface IRecorderCacheProducerSync { /* Clears cache data */ clear(): void /* Pops cache data */ - pop(): T + pop(toMerge?: T): T } @@ -352,7 +352,7 @@ export interface IImpressionCountsCacheSync extends IRecorderCacheProducerSync // pop cache data + pop(toMerge?: Record ): Record // pop cache data } diff --git a/src/sync/submitters/submitter.ts b/src/sync/submitters/submitter.ts index 28e1489e..29dd81a6 100644 --- a/src/sync/submitters/submitter.ts +++ b/src/sync/submitters/submitter.ts @@ -23,14 +23,9 @@ export function submitterFactory( let data: any; function postData(): Promise { - if (data) { - // if data is an array, append new items from cache - if (Array.isArray(data) && !sourceCache.isEmpty()) data = data.concat(sourceCache.pop()); - } else { - if (sourceCache.isEmpty()) return Promise.resolve(); - // we clear the cache to track new items, while `data` is used for retries - data = sourceCache.pop(); - } + if (sourceCache.isEmpty() && !data) return Promise.resolve(); + // we clear the cache to track new items, while `data` is used for retries + data = sourceCache.pop(data); const dataCountMessage = typeof data.length === 'number' ? `${data.length} ${dataName}` : dataName; log[debugLogs ? 'debug' : 'info'](SUBMITTERS_PUSH, [dataCountMessage]); From 417016916e95f63263a1a32e0d04d1965782fd18 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 13 Jun 2022 14:39:31 -0300 Subject: [PATCH 09/11] remove ambiguous comment --- src/sync/submitters/submitter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sync/submitters/submitter.ts b/src/sync/submitters/submitter.ts index 29dd81a6..2cf16d7c 100644 --- a/src/sync/submitters/submitter.ts +++ b/src/sync/submitters/submitter.ts @@ -24,7 +24,6 @@ export function submitterFactory( function postData(): Promise { if (sourceCache.isEmpty() && !data) return Promise.resolve(); - // we clear the cache to track new items, while `data` is used for retries data = sourceCache.pop(data); const dataCountMessage = typeof data.length === 'number' ? `${data.length} ${dataName}` : dataName; From 6cb5bd7c80e0a936242127fca6c792d74e12f0b6 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 13 Jun 2022 15:43:45 -0300 Subject: [PATCH 10/11] updates on unit tests --- .../__tests__/eventsSubmitter.spec.ts | 10 ++++++++-- .../__tests__/impressionsSubmitter.spec.ts | 18 ++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/sync/submitters/__tests__/eventsSubmitter.spec.ts b/src/sync/submitters/__tests__/eventsSubmitter.spec.ts index 60de0379..d8336b4f 100644 --- a/src/sync/submitters/__tests__/eventsSubmitter.spec.ts +++ b/src/sync/submitters/__tests__/eventsSubmitter.spec.ts @@ -80,14 +80,20 @@ describe('Events submitter', () => { eventsCacheInMemory.track({ eventTypeId: 'event1', timestamp: 1 }); eventsSubmitter.start(); + expect(params.splitApi.postEventsBulk.mock.calls).toEqual([['[{"eventTypeId":"event1","timestamp":1}]']]); + // Tracking event when POST is pending eventsCacheInMemory.track({ eventTypeId: 'event2', timestamp: 1 }); // Tracking event when POST is resolved setTimeout(() => { eventsCacheInMemory.track({ eventTypeId: 'event3', timestamp: 1 }); }); setTimeout(() => { - expect(params.splitApi.postEventsBulk).toBeCalledTimes(2); - expect(params.splitApi.postEventsBulk.mock.calls).toEqual([['[{"eventTypeId":"event1","timestamp":1}]'], ['[{"eventTypeId":"event2","timestamp":1},{"eventTypeId":"event3","timestamp":1}]']]); + expect(params.splitApi.postEventsBulk.mock.calls).toEqual([ + // POST with event1 + ['[{"eventTypeId":"event1","timestamp":1}]'], + // POST with event2 and event3 + ['[{"eventTypeId":"event2","timestamp":1},{"eventTypeId":"event3","timestamp":1}]'] + ]); eventsSubmitter.stop(); done(); diff --git a/src/sync/submitters/__tests__/impressionsSubmitter.spec.ts b/src/sync/submitters/__tests__/impressionsSubmitter.spec.ts index d5c74144..4f59fb19 100644 --- a/src/sync/submitters/__tests__/impressionsSubmitter.spec.ts +++ b/src/sync/submitters/__tests__/impressionsSubmitter.spec.ts @@ -10,6 +10,8 @@ const imp1 = { treatment: 'someTreatment', time: 0 }; +const imp2 = { ...imp1, keyName: 'k2' }; +const imp3 = { ...imp1, keyName: 'k3' }; describe('Impressions submitter', () => { @@ -32,15 +34,15 @@ describe('Impressions submitter', () => { impressionsSubmitter.start(); // Tracking impression when POST is pending - impressionsCacheInMemory.track([{ ...imp1, keyName: 'k2' }]); + impressionsCacheInMemory.track([imp2]); // Tracking impression after POST is resolved - setTimeout(() => { impressionsCacheInMemory.track([{ ...imp1, keyName: 'k3', }]); }); + setTimeout(() => { impressionsCacheInMemory.track([imp3]); }); setTimeout(() => { expect(params.splitApi.postTestImpressionsBulk.mock.calls).toEqual([ - // impression for k1 + // POST with imp1 ['[{"f":"someFeature","i":[{"k":"k1","t":"someTreatment","m":0,"c":123}]}]'], - // impressions for k2 and k3 + // POST with imp2 and imp3 ['[{"f":"someFeature","i":[{"k":"k2","t":"someTreatment","m":0,"c":123},{"k":"k3","t":"someTreatment","m":0,"c":123}]}]']]); impressionsSubmitter.stop(); @@ -56,15 +58,15 @@ describe('Impressions submitter', () => { impressionsSubmitter.start(); // Tracking impression when POST is pending - impressionsCacheInMemory.track([{ ...imp1, keyName: 'k2' }]); + impressionsCacheInMemory.track([imp2]); // Tracking impression after POST is rejected - setTimeout(() => { impressionsCacheInMemory.track([{ ...imp1, keyName: 'k3', }]); }); + setTimeout(() => { impressionsCacheInMemory.track([imp3]); }); setTimeout(() => { expect(params.splitApi.postTestImpressionsBulk.mock.calls).toEqual([ - // impression for k1 + // impression for imp1 ['[{"f":"someFeature","i":[{"k":"k1","t":"someTreatment","m":0,"c":123}]}]'], - // impressions for k1, k2 and k3 + // impressions for imp1, imp2 and imp3 ['[{"f":"someFeature","i":[{"k":"k1","t":"someTreatment","m":0,"c":123},{"k":"k2","t":"someTreatment","m":0,"c":123},{"k":"k3","t":"someTreatment","m":0,"c":123}]}]']]); impressionsSubmitter.stop(); From 0a76ba527791f834632880f084714386cf94b561 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Mon, 13 Jun 2022 15:48:45 -0300 Subject: [PATCH 11/11] prepare stable release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f3b792a..c959e174 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.4.1-rc.2", + "version": "1.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d7f18b67..d9951490 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.4.1-rc.2", + "version": "1.4.1", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js",