Skip to content

Commit 17bbd36

Browse files
Merge pull request #392 from splitio/rb_segments_splitschanges_updater
[Rule-based segments] Update `splitsChangesUpdater` and `splitsChangesFetcher`
2 parents bef9252 + 680b82d commit 17bbd36

File tree

12 files changed

+1468
-1396
lines changed

12 files changed

+1468
-1396
lines changed

src/__tests__/mocks/splitchanges.since.-1.json

Lines changed: 1330 additions & 1325 deletions
Large diffs are not rendered by default.

src/dtos/types.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,16 @@ export type ISplitPartial = Pick<ISplit, 'conditions' | 'configurations' | 'traf
228228

229229
/** Interface of the parsed JSON response of `/splitChanges` */
230230
export interface ISplitChangesResponse {
231-
till: number,
232-
splits: ISplit[]
231+
ff?: {
232+
t: number,
233+
s?: number,
234+
d: ISplit[]
235+
},
236+
rbs?: {
237+
t: number,
238+
s?: number,
239+
d: IRBSegment[]
240+
}
233241
}
234242

235243
/** Interface of the parsed JSON response of `/segmentChanges/{segmentName}` */

src/logger/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const RETRIEVE_MANAGER = 29;
2121
export const SYNC_OFFLINE_DATA = 30;
2222
export const SYNC_SPLITS_FETCH = 31;
2323
export const SYNC_SPLITS_UPDATE = 32;
24+
export const SYNC_RBS_UPDATE = 33;
2425
export const STREAMING_NEW_MESSAGE = 35;
2526
export const SYNC_TASK_START = 36;
2627
export const SYNC_TASK_EXECUTE = 37;

src/logger/messages/debug.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ export const codesDebug: [number, string][] = codesInfo.concat([
2020
[c.RETRIEVE_MANAGER, 'Retrieving manager instance.'],
2121
// synchronizer
2222
[c.SYNC_OFFLINE_DATA, c.LOG_PREFIX_SYNC_OFFLINE + 'Feature flags data: \n%s'],
23-
[c.SYNC_SPLITS_FETCH, c.LOG_PREFIX_SYNC_SPLITS + 'Spin up feature flags update using since = %s'],
24-
[c.SYNC_SPLITS_UPDATE, c.LOG_PREFIX_SYNC_SPLITS + 'New feature flags %s. Removed feature flags %s. Segment names collected %s'],
23+
[c.SYNC_SPLITS_FETCH, c.LOG_PREFIX_SYNC_SPLITS + 'Spin up feature flags update using since = %s and rbSince = %s.'],
24+
[c.SYNC_SPLITS_UPDATE, c.LOG_PREFIX_SYNC_SPLITS + 'New feature flags %s. Removed feature flags %s.'],
25+
[c.SYNC_RBS_UPDATE, c.LOG_PREFIX_SYNC_SPLITS + 'New rule-based segments %s. Removed rule-based segments %s.'],
2526
[c.STREAMING_NEW_MESSAGE, c.LOG_PREFIX_SYNC_STREAMING + 'New SSE message received, with data: %s.'],
2627
[c.SYNC_TASK_START, c.LOG_PREFIX_SYNC + ': Starting %s. Running each %s millis'],
2728
[c.SYNC_TASK_EXECUTE, c.LOG_PREFIX_SYNC + ': Running %s'],

src/services/__tests__/splitApi.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ describe('splitApi', () => {
4040
assertHeaders(settings, headers);
4141
expect(url).toBe('sdk/segmentChanges/segmentName?since=-1&till=90');
4242

43-
splitApi.fetchSplitChanges(-1, false, 100);
43+
splitApi.fetchSplitChanges(-1, false, 100, -1);
4444
[url, { headers }] = fetchMock.mock.calls[3];
4545
assertHeaders(settings, headers);
46-
expect(url).toBe(expecteFlagsUrl(-1, 100, settings.validateFilters || false, settings));
46+
expect(url).toBe(expectedFlagsUrl(-1, 100, settings.validateFilters || false, settings, -1));
4747

4848
splitApi.postEventsBulk('fake-body');
4949
assertHeaders(settings, fetchMock.mock.calls[4][1].headers);
@@ -66,9 +66,9 @@ describe('splitApi', () => {
6666
fetchMock.mockClear();
6767

6868

69-
function expecteFlagsUrl(since: number, till: number, usesFilter: boolean, settings: ISettings) {
69+
function expectedFlagsUrl(since: number, till: number, usesFilter: boolean, settings: ISettings, rbSince?: number) {
7070
const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
71-
return `sdk/splitChanges?s=1.1&since=${since}${usesFilter ? filterQueryString : ''}${till ? '&till=' + till : ''}`;
71+
return `sdk/splitChanges?s=1.1&since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${usesFilter ? filterQueryString : ''}${till ? '&till=' + till : ''}`;
7272
}
7373
});
7474

src/services/splitApi.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ export function splitApiFactory(
5353
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
5454
},
5555

56-
fetchSplitChanges(since: number, noCache?: boolean, till?: number) {
57-
const url = `${urls.sdk}/splitChanges?s=${flagSpecVersion}&since=${since}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
56+
fetchSplitChanges(since: number, noCache?: boolean, till?: number, rbSince?: number) {
57+
const url = `${urls.sdk}/splitChanges?s=${flagSpecVersion}&since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
5858
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS))
5959
.catch((err) => {
6060
if (err.statusCode === 414) settings.log.error(ERROR_TOO_MANY_SETS);

src/services/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export type ISplitHttpClient = (url: string, options?: IRequestOptions, latencyT
3535

3636
export type IFetchAuth = (userKeys?: string[]) => Promise<IResponse>
3737

38-
export type IFetchSplitChanges = (since: number, noCache?: boolean, till?: number) => Promise<IResponse>
38+
export type IFetchSplitChanges = (since: number, noCache?: boolean, till?: number, rbSince?: number) => Promise<IResponse>
3939

4040
export type IFetchSegmentChanges = (since: number, segmentName: string, noCache?: boolean, till?: number) => Promise<IResponse>
4141

src/sync/polling/fetchers/splitChangesFetcher.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges
1111
since: number,
1212
noCache?: boolean,
1313
till?: number,
14+
rbSince?: number,
1415
// Optional decorator for `fetchSplitChanges` promise, such as timeout or time tracker
1516
decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
1617
) {
1718

18-
let splitsPromise = fetchSplitChanges(since, noCache, till);
19+
let splitsPromise = fetchSplitChanges(since, noCache, till, rbSince);
1920
if (decorator) splitsPromise = decorator(splitsPromise);
2021

2122
return splitsPromise.then(resp => resp.json());

src/sync/polling/fetchers/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type ISplitChangesFetcher = (
55
since: number,
66
noCache?: boolean,
77
till?: number,
8+
rbSince?: number,
89
decorator?: (promise: Promise<IResponse>) => Promise<IResponse>
910
) => Promise<ISplitChangesResponse>
1011

src/sync/polling/updaters/__tests__/splitChangesUpdater.spec.ts

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import { ISplit } from '../../../../dtos/types';
1+
import { IRBSegment, ISplit } from '../../../../dtos/types';
22
import { readinessManagerFactory } from '../../../../readiness/readinessManager';
33
import { splitApiFactory } from '../../../../services/splitApi';
44
import { SegmentsCacheInMemory } from '../../../../storages/inMemory/SegmentsCacheInMemory';
55
import { SplitsCacheInMemory } from '../../../../storages/inMemory/SplitsCacheInMemory';
66
import { splitChangesFetcherFactory } from '../../fetchers/splitChangesFetcher';
7-
import { splitChangesUpdaterFactory, parseSegments, computeSplitsMutation } from '../splitChangesUpdater';
7+
import { splitChangesUpdaterFactory, parseSegments, computeMutation } from '../splitChangesUpdater';
88
import splitChangesMock1 from '../../../../__tests__/mocks/splitchanges.since.-1.json';
99
import fetchMock from '../../../../__tests__/testUtils/fetchMock';
1010
import { fullSettings, settingsSplitApi } from '../../../../utils/settingsValidation/__tests__/settings.mocks';
1111
import { EventEmitter } from '../../../../utils/MinEvents';
1212
import { loggerMock } from '../../../../logger/__tests__/sdkLogger.mock';
1313
import { telemetryTrackerFactory } from '../../../../trackers/telemetryTracker';
1414
import { splitNotifications } from '../../../streaming/__tests__/dataMocks';
15+
import { RBSegmentsCacheInMemory } from '../../../../storages/inMemory/RBSegmentsCacheInMemory';
1516

1617
const ARCHIVED_FF = 'ARCHIVED';
1718

@@ -94,58 +95,60 @@ test('splitChangesUpdater / segments parser', () => {
9495
test('splitChangesUpdater / compute splits mutation', () => {
9596
const splitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] };
9697

97-
let splitsMutation = computeSplitsMutation([activeSplitWithSegments, archivedSplit] as ISplit[], splitFiltersValidation);
98+
let segments = new Set<string>();
99+
let splitsMutation = computeMutation([activeSplitWithSegments, archivedSplit] as ISplit[], segments, splitFiltersValidation);
98100

99101
expect(splitsMutation.added).toEqual([activeSplitWithSegments]);
100102
expect(splitsMutation.removed).toEqual([archivedSplit]);
101-
expect(splitsMutation.segments).toEqual(['A', 'B']);
103+
expect(Array.from(segments)).toEqual(['A', 'B']);
102104

103105
// SDK initialization without sets
104106
// should process all the notifications
105-
splitsMutation = computeSplitsMutation([testFFSetsAB, test2FFSetsX] as ISplit[], splitFiltersValidation);
107+
segments = new Set<string>();
108+
splitsMutation = computeMutation([testFFSetsAB, test2FFSetsX] as ISplit[], segments, splitFiltersValidation);
106109

107110
expect(splitsMutation.added).toEqual([testFFSetsAB, test2FFSetsX]);
108111
expect(splitsMutation.removed).toEqual([]);
109-
expect(splitsMutation.segments).toEqual([]);
112+
expect(Array.from(segments)).toEqual([]);
110113
});
111114

112115
test('splitChangesUpdater / compute splits mutation with filters', () => {
113116
// SDK initialization with sets: [set_a, set_b]
114117
let splitFiltersValidation = { queryString: '&sets=set_a,set_b', groupedFilters: { bySet: ['set_a', 'set_b'], byName: ['name_1'], byPrefix: [] }, validFilters: [] };
115118

116119
// fetching new feature flag in sets A & B
117-
let splitsMutation = computeSplitsMutation([testFFSetsAB], splitFiltersValidation);
120+
let splitsMutation = computeMutation([testFFSetsAB], new Set(), splitFiltersValidation);
118121

119122
// should add it to mutations
120123
expect(splitsMutation.added).toEqual([testFFSetsAB]);
121124
expect(splitsMutation.removed).toEqual([]);
122125

123126
// fetching existing test feature flag removed from set B
124-
splitsMutation = computeSplitsMutation([testFFRemoveSetB], splitFiltersValidation);
127+
splitsMutation = computeMutation([testFFRemoveSetB], new Set(), splitFiltersValidation);
125128

126129
expect(splitsMutation.added).toEqual([testFFRemoveSetB]);
127130
expect(splitsMutation.removed).toEqual([]);
128131

129132
// fetching existing test feature flag removed from set B
130-
splitsMutation = computeSplitsMutation([testFFRemoveSetA], splitFiltersValidation);
133+
splitsMutation = computeMutation([testFFRemoveSetA], new Set(), splitFiltersValidation);
131134

132135
expect(splitsMutation.added).toEqual([]);
133136
expect(splitsMutation.removed).toEqual([testFFRemoveSetA]);
134137

135138
// fetching existing test feature flag removed from set B
136-
splitsMutation = computeSplitsMutation([testFFEmptySet], splitFiltersValidation);
139+
splitsMutation = computeMutation([testFFEmptySet], new Set(), splitFiltersValidation);
137140

138141
expect(splitsMutation.added).toEqual([]);
139142
expect(splitsMutation.removed).toEqual([testFFEmptySet]);
140143

141144
// SDK initialization with names: ['test2']
142145
splitFiltersValidation = { queryString: '&names=test2', groupedFilters: { bySet: [], byName: ['test2'], byPrefix: [] }, validFilters: [] };
143-
splitsMutation = computeSplitsMutation([testFFSetsAB], splitFiltersValidation);
146+
splitsMutation = computeMutation([testFFSetsAB], new Set(), splitFiltersValidation);
144147

145148
expect(splitsMutation.added).toEqual([]);
146149
expect(splitsMutation.removed).toEqual([testFFSetsAB]);
147150

148-
splitsMutation = computeSplitsMutation([test2FFSetsX, testFFEmptySet], splitFiltersValidation);
151+
splitsMutation = computeMutation([test2FFSetsX, testFFEmptySet], new Set(), splitFiltersValidation);
149152

150153
expect(splitsMutation.added).toEqual([test2FFSetsX]);
151154
expect(splitsMutation.removed).toEqual([testFFEmptySet]);
@@ -161,10 +164,13 @@ describe('splitChangesUpdater', () => {
161164
const splits = new SplitsCacheInMemory();
162165
const updateSplits = jest.spyOn(splits, 'update');
163166

167+
const rbSegments = new RBSegmentsCacheInMemory();
168+
const updateRbSegments = jest.spyOn(rbSegments, 'update');
169+
164170
const segments = new SegmentsCacheInMemory();
165171
const registerSegments = jest.spyOn(segments, 'registerSegments');
166172

167-
const storage = { splits, segments };
173+
const storage = { splits, rbSegments, segments };
168174

169175
const readinessManager = readinessManagerFactory(EventEmitter, fullSettings);
170176
const splitsEmitSpy = jest.spyOn(readinessManager.splits, 'emit');
@@ -179,22 +185,29 @@ describe('splitChangesUpdater', () => {
179185

180186
test('test without payload', async () => {
181187
const result = await splitChangesUpdater();
188+
189+
expect(fetchSplitChanges).toBeCalledTimes(1);
190+
expect(fetchSplitChanges).lastCalledWith(-1, undefined, undefined, -1);
182191
expect(updateSplits).toBeCalledTimes(1);
183-
expect(updateSplits).lastCalledWith(splitChangesMock1.splits, [], splitChangesMock1.till);
192+
expect(updateSplits).lastCalledWith(splitChangesMock1.ff.d, [], splitChangesMock1.ff.t);
193+
expect(updateRbSegments).toBeCalledTimes(0); // no rbSegments to update
184194
expect(registerSegments).toBeCalledTimes(1);
185195
expect(splitsEmitSpy).toBeCalledWith('state::splits-arrived');
186196
expect(result).toBe(true);
187197
});
188198

189-
test('test with payload', async () => {
199+
test('test with ff payload', async () => {
190200
let index = 0;
191201
for (const notification of splitNotifications) {
192202
const payload = notification.decoded as Pick<ISplit, 'name' | 'changeNumber' | 'killed' | 'defaultTreatment' | 'trafficTypeName' | 'conditions' | 'status' | 'seed' | 'trafficAllocation' | 'trafficAllocationSeed' | 'configurations'>;
193203
const changeNumber = payload.changeNumber;
194204

195205
await expect(splitChangesUpdater(undefined, undefined, { payload, changeNumber: changeNumber })).resolves.toBe(true);
196-
// fetch not being called
206+
207+
// fetch and RBSegments.update not being called
197208
expect(fetchSplitChanges).toBeCalledTimes(0);
209+
expect(updateRbSegments).toBeCalledTimes(0);
210+
198211
expect(updateSplits).toBeCalledTimes(index + 1);
199212
// Change number being updated
200213
expect(updateSplits.mock.calls[index][2]).toEqual(changeNumber);
@@ -209,6 +222,23 @@ describe('splitChangesUpdater', () => {
209222
}
210223
});
211224

225+
test('test with rbsegment payload', async () => {
226+
const payload = { name: 'rbsegment', status: 'ACTIVE', changeNumber: 1684329854385, conditions: [] } as unknown as IRBSegment;
227+
const changeNumber = payload.changeNumber;
228+
229+
await expect(splitChangesUpdater(undefined, undefined, { payload, changeNumber: changeNumber })).resolves.toBe(true);
230+
231+
// fetch and Splits.update not being called
232+
expect(fetchSplitChanges).toBeCalledTimes(0);
233+
expect(updateSplits).toBeCalledTimes(0);
234+
235+
expect(updateRbSegments).toBeCalledTimes(1);
236+
expect(updateRbSegments).toBeCalledWith([payload], [], changeNumber);
237+
238+
expect(registerSegments).toBeCalledTimes(1);
239+
expect(registerSegments).toBeCalledWith([]);
240+
});
241+
212242
test('flag sets splits-arrived emission', async () => {
213243
const payload = splitNotifications[3].decoded as Pick<ISplit, 'name' | 'changeNumber' | 'killed' | 'defaultTreatment' | 'trafficTypeName' | 'conditions' | 'status' | 'seed' | 'trafficAllocation' | 'trafficAllocationSeed' | 'configurations'>;
214244
const setMocks = [

0 commit comments

Comments
 (0)