Skip to content

Commit

Permalink
Merge pull request #777 from splitio/development
Browse files Browse the repository at this point in the history
Release v10.24.0
  • Loading branch information
EmilianoSanchez authored Dec 4, 2023
2 parents 4f8aabf + 5a9f7ac commit 400d93c
Show file tree
Hide file tree
Showing 42 changed files with 2,343 additions and 320 deletions.
16 changes: 15 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
10.24.0 (December 4, 2023)
- Added support for Flag Sets on the SDK, which enables grouping feature flags and interacting with the group rather than individually (more details in our documentation):
- Added new variations of the get treatment methods to support evaluating flags in given flag set/s.
- getTreatmentsByFlagSet and getTreatmentsByFlagSets
- getTreatmentsWithConfigByFlagSets and getTreatmentsWithConfigByFlagSets
- Added a new optional Split Filter configuration option. This allows the SDK and Split services to only synchronize the flags in the specified flag sets, avoiding unused or unwanted flags from being synced on the SDK instance, bringing all the benefits from a reduced payload.
- Note: Only applicable when the SDK is in charge of the rollout data synchronization. When not applicable, the SDK will log a warning on init.
- Added `sets` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager to expose flag sets on flag views.
- Added `defaultTreatment` property to the `SplitView` object returned by the `split` and `splits` methods of the SDK manager (Related to issue https://github.com/splitio/javascript-commons/issues/225).
- Updated @splitsoftware/splitio-commons package to version 1.12.0 that includes vulnerability fixes, flag sets support, and other improvements.
- Updated Redis adapter to handle timeouts and queueing of some missing commands: 'hincrby', 'popNRaw', and 'pipeline.exec'.
- Bugfixing - Fixed manager methods in consumer modes to return results in a promise when the SDK is not operational (not ready or destroyed).
- Bugfixing - Fixed SDK key validation in NodeJS to ensure the SDK_READY_TIMED_OUT event is emitted when a client-side type SDK key is provided instead of a server-side one (Related to issue https://github.com/splitio/javascript-client/issues/768).

10.23.1 (September 22, 2023)
- Updated @splitsoftware/splitio-commons package to version 1.9.1. This update removes the handler for 'unload' DOM events, that can prevent browsers from being able to put pages in the back/forward cache for faster back and forward loads (Related to issue https://github.com/splitio/javascript-client/issues/759).

10.23.0 (July 18, 2023)
- Updated @splitsoftware/splitio-commons package to version 1.8.4 that includes minor improvements:
- Updated @splitsoftware/splitio-commons package to version 1.9.0 that includes minor improvements:
- Updated streaming architecture implementation to apply feature flag updates from the notification received which is now enhanced, improving efficiency and reliability of the whole update system.

10.22.6 (July 6, 2023)
Expand Down
396 changes: 265 additions & 131 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio",
"version": "10.23.1",
"version": "10.24.0",
"description": "Split SDK",
"files": [
"README.md",
Expand Down Expand Up @@ -40,7 +40,7 @@
"node": ">=6"
},
"dependencies": {
"@splitsoftware/splitio-commons": "1.9.1",
"@splitsoftware/splitio-commons": "1.12.0",
"@types/google.analytics": "0.0.40",
"@types/ioredis": "^4.28.0",
"bloom-filters": "^3.0.0",
Expand Down Expand Up @@ -100,7 +100,7 @@
"check:version": "cross-env NODE_ENV=test tape -r ./ts-node.register src/settings/__tests__/defaults.spec.js",
"test-browser": "npm run test-browser-unit && npm run test-browser-e2e",
"test-browser-unit": "cross-env NODE_ENV=test karma start karma/unit.karma.conf.js",
"test-browser-e2e": "npm run test-browser-e2e-online && npm run test-browser-e2e-offline && npm run test-browser-e2e-destroy && npm run test-browser-e2e-errorCatching && npm run test-browser-e2e-push && npm run test-browser-e2e-gaIntegration",
"test-browser-e2e": "npm run test-browser-e2e-online && npm run test-browser-e2e-offline && npm run test-browser-e2e-destroy && npm run test-browser-e2e-errorCatching && npm run test-browser-e2e-push",
"test-browser-e2e-online": "cross-env NODE_ENV=test karma start karma/e2e.online.karma.conf.js",
"test-browser-e2e-offline": "cross-env NODE_ENV=test karma start karma/e2e.offline.karma.conf.js",
"test-browser-e2e-destroy": "cross-env NODE_ENV=test karma start karma/e2e.destroy.karma.conf.js",
Expand Down
40 changes: 39 additions & 1 deletion src/__tests__/browserSuites/fetch-specific-splits.spec.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sinon from 'sinon';
import { SplitFactory } from '../../';
import { splitFilters, queryStrings, groupedFilters } from '../mocks/fetchSpecificSplits';

Expand All @@ -12,7 +13,7 @@ const baseConfig = {
streamingEnabled: false,
};

export default function fetchSpecificSplits(fetchMock, assert) {
export function fetchSpecificSplits(fetchMock, assert) {

assert.plan(splitFilters.length);

Expand Down Expand Up @@ -46,3 +47,40 @@ export default function fetchSpecificSplits(fetchMock, assert) {

}
}

export function fetchSpecificSplitsForFlagSets(fetchMock, assert) {
// Flag sets
assert.test(async (t) => {

const splitFilters = [{ type: 'bySet', values: ['set_x ', 'set_x', 'set_3', 'set_2', 'set_3', 'set_ww', 'invalid+', '_invalid', '4_valid'] }];
const baseUrls = { sdk: 'https://sdk.baseurl' };

const config = {
...baseConfig,
urls: baseUrls,
debug: 'WARN',
sync: {
splitFilters
}
};

const logSpy = sinon.spy(console, 'log');

let factory;
const queryString = '&sets=4_valid,set_2,set_3,set_ww,set_x';
fetchMock.get(baseUrls.sdk + '/mySegments/nicolas%40split.io', { status: 200, body: { 'mySegments': [] } });

fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=-1' + queryString, { status: 200, body: { splits: [], since: 1457552620999, till: 1457552620999 }});
fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=1457552620999' + queryString, async function () {
t.pass('flag set query correctly formed');
t.true(logSpy.calledWithExactly('[WARN] splitio => settings: bySet filter value "set_x " has extra whitespace, trimming.'));
t.true(logSpy.calledWithExactly('[WARN] splitio => settings: you passed invalid+, flag set must adhere to the regular expressions /^[a-z0-9][_a-z0-9]{0,49}$/. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. invalid+ was discarded.'));
t.true(logSpy.calledWithExactly('[WARN] splitio => settings: you passed _invalid, flag set must adhere to the regular expressions /^[a-z0-9][_a-z0-9]{0,49}$/. This means a flag set must start with a letter or number, be in lowercase, alphanumeric and have a max length of 50 characters. _invalid was discarded.'));
logSpy.restore();
factory.client().destroy().then(() => {
t.end();
});
});
factory = SplitFactory(config);
}, 'FlagSets config');
}
204 changes: 204 additions & 0 deletions src/__tests__/browserSuites/flag-sets.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { SplitFactory } from '../..';

import splitChange2 from '../mocks/splitchanges.since.-1.till.1602796638344.json';
import splitChange1 from '../mocks/splitchanges.since.1602796638344.till.1602797638344.json';
import splitChange0 from '../mocks/splitchanges.since.1602797638344.till.1602798638344.json';

const baseUrls = { sdk: 'https://sdk.baseurl' };

const baseConfig = {
core: {
authorizationKey: '<fake-token>',
key: '[email protected]'
},
urls: baseUrls,
scheduler: { featuresRefreshRate: 0.01 },
streamingEnabled: false
};

export default function flagSets(fetchMock, t) {
fetchMock.get(baseUrls.sdk + '/mySegments/nicolas%40split.io', { status: 200, body: { 'mySegments': [] } });

t.test(async (assert) => {
let factory;
let manager;

// Receive split change with 1 split belonging to set_1 & set_2 and one belonging to set_3
fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=-1&sets=set_1,set_2', function () {
return { status: 200, body: splitChange2};
});

// Receive split change with 1 split belonging to set_1 only
fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=1602796638344&sets=set_1,set_2', function () {
// stored feature flags before update
const storedFlags = manager.splits();
assert.true(storedFlags.length === 1, 'only one feature flag should be added');
assert.true(storedFlags[0].name === 'workm');
assert.deepEqual(storedFlags[0].sets, ['set_1','set_2']);

// send split change
return { status: 200, body: splitChange1};
});

// Receive split change with 1 split belonging to set_3 only
fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=1602797638344&sets=set_1,set_2', function () {
// stored feature flags before update
const storedFlags = manager.splits();
assert.true(storedFlags.length === 1);
assert.true(storedFlags[0].name === 'workm');
assert.deepEqual(storedFlags[0].sets, ['set_1'], 'the feature flag should be updated');

// send split change
return { status: 200, body: splitChange0};
});

fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=1602798638344&sets=set_1,set_2', async function () {
// stored feature flags before update
const storedFlags = manager.splits();
assert.true(storedFlags.length === 0, 'the feature flag should be removed');
await factory.client().destroy();
assert.end();

return { status: 200, body: {} };
});

// Initialize a factory with polling and sets set_1 & set_2 configured.
const splitFilters = [{ type: 'bySet', values: ['set_1','set_2'] }];
factory = SplitFactory({ ...baseConfig, sync: { splitFilters }});
await factory.client().ready();
manager = factory.manager();

}, 'Polling - SDK with sets configured updates flags according to sets');

t.test(async (assert) => {
let factory;
let manager;

// Receive split change with 1 split belonging to set_1 & set_2 and one belonging to set_3
fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=-1', function () {
return { status: 200, body: splitChange2};
});

// Receive split change with 1 split belonging to set_1 only
fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=1602796638344', function () {
// stored feature flags before update
const storedFlags = manager.splits();
assert.true(storedFlags.length === 2, 'every feature flag should be added');
assert.true(storedFlags[0].name === 'workm');
assert.true(storedFlags[1].name === 'workm_set_3');
assert.deepEqual(storedFlags[0].sets, ['set_1','set_2']);
assert.deepEqual(storedFlags[1].sets, ['set_3']);

// send split change
return { status: 200, body: splitChange1};
});

// Receive split change with 1 split belonging to set_3 only
fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=1602797638344', function () {
// stored feature flags before update
const storedFlags = manager.splits();
assert.true(storedFlags.length === 2);
assert.true(storedFlags[0].name === 'workm');
assert.true(storedFlags[1].name === 'workm_set_3');
assert.deepEqual(storedFlags[0].sets, ['set_1'], 'the feature flag should be updated');
assert.deepEqual(storedFlags[1].sets, ['set_3'], 'the feature flag should remain as it was');

// send split change
return { status: 200, body: splitChange0};
});

fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=1602798638344', async function () {
// stored feature flags before update
const storedFlags = manager.splits();
assert.true(storedFlags.length === 2);
assert.true(storedFlags[0].name === 'workm');
assert.true(storedFlags[1].name === 'workm_set_3');
assert.deepEqual(storedFlags[0].sets, ['set_3'], 'the feature flag should be updated');
assert.deepEqual(storedFlags[1].sets, ['set_3'], 'the feature flag should remain as it was');
await factory.client().destroy();
assert.end();
return { status: 200, body: {} };
});

// Initialize a factory with polling and no sets configured.
factory = SplitFactory(baseConfig);
await factory.client().ready();
manager = factory.manager();

}, 'Poling - SDK with no sets configured does not take sets into account when updating flags');

// EVALUATION

t.test(async (assert) => {
fetchMock.reset();
fetchMock.post('*', 200);

let factory, client = [];

fetchMock.get(baseUrls.sdk + '/mySegments/nicolas%40split.io', { status: 200, body: { 'mySegments': [] } });
// Receive split change with 1 split belonging to set_1 & set_2 and one belonging to set_3
fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=-1&sets=set_1', function () {
return { status: 200, body: splitChange2};
});

fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=1602796638344&sets=set_1', async function () {
// stored feature flags before update
assert.deepEqual(client.getTreatmentsByFlagSet('set_1'), {workm: 'on'}, 'only the flag in set_1 can be evaluated');
assert.deepEqual(client.getTreatmentsByFlagSet('set_2'), {}, 'only the flag in set_1 can be evaluated');
assert.deepEqual(client.getTreatmentsByFlagSet('set_3'), {}, 'only the flag in set_1 can be evaluated');
assert.deepEqual(client.getTreatmentsWithConfigByFlagSet('set_1'), { workm: { treatment: 'on', config: null } }, 'only the flag in set_1 can be evaluated');
assert.deepEqual(client.getTreatmentsWithConfigByFlagSet('set_2'), {}, 'only the flag in set_1 can be evaluated');
assert.deepEqual(client.getTreatmentsWithConfigByFlagSet('set_3'), {}, 'only the flag in set_1 can be evaluated');
assert.deepEqual(client.getTreatmentsByFlagSets(['set_1','set_2','set_3']), {workm: 'on'}, 'only the flag in set_1 can be evaluated');
assert.deepEqual(client.getTreatmentsWithConfigByFlagSets(['set_1','set_2','set_3']), { workm: { treatment: 'on', config: null } }, 'only the flag in set_1 can be evaluated');
await client.destroy();
assert.end();

// send split change
return { status: 200, body: splitChange1};
});

// Initialize a factory with set_1 configured.
const splitFilters = [{ type: 'bySet', values: ['set_1'] }];
factory = SplitFactory({ ...baseConfig, sync: { splitFilters }});
client = factory.client();
await client.ready();

}, 'SDK with sets configured can only evaluate configured sets');

t.test(async (assert) => {
fetchMock.reset();
fetchMock.post('*', 200);

let factory, client = [];

fetchMock.get(baseUrls.sdk + '/mySegments/nicolas%40split.io', { status: 200, body: { 'mySegments': [] } });
// Receive split change with 1 split belonging to set_1 & set_2 and one belonging to set_3
fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=-1', function () {
return { status: 200, body: splitChange2};
});

fetchMock.getOnce(baseUrls.sdk + '/splitChanges?since=1602796638344', async function () {
// stored feature flags before update
assert.deepEqual(client.getTreatmentsByFlagSet('set_1'), {workm: 'on'}, 'all flags can be evaluated');
assert.deepEqual(client.getTreatmentsByFlagSet('set_2'), {workm: 'on'}, 'all flags can be evaluated');
assert.deepEqual(client.getTreatmentsByFlagSet('set_3'), { workm_set_3: 'on' }, 'all flags can be evaluated');
assert.deepEqual(client.getTreatmentsWithConfigByFlagSet('set_1'), { workm: { treatment: 'on', config: null } }, 'all flags can be evaluated');
assert.deepEqual(client.getTreatmentsWithConfigByFlagSet('set_2'), { workm: { treatment: 'on', config: null } }, 'all flags can be evaluated');
assert.deepEqual(client.getTreatmentsWithConfigByFlagSet('set_3'), { workm_set_3: { treatment: 'on', config: null } }, 'all flags can be evaluated');
assert.deepEqual(client.getTreatmentsByFlagSets(['set_1','set_2','set_3']), { workm: 'on', workm_set_3: 'on' }, 'all flags can be evaluated');
assert.deepEqual(client.getTreatmentsWithConfigByFlagSets(['set_1','set_2','set_3']), { workm: { treatment: 'on', config: null }, workm_set_3: { treatment: 'on', config: null } }, 'all flags can be evaluated');
await client.destroy();
assert.end();

// send split change
return { status: 200, body: splitChange1};
});

factory = SplitFactory(baseConfig);
client = factory.client();
await client.ready();

}, 'SDK with no sets configured can evaluate any set');

}
4 changes: 3 additions & 1 deletion src/__tests__/browserSuites/manager.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export default async function (settings, fetchMock, assert) {
'killed': mockSplits.splits[index].killed,
'changeNumber': mockSplits.splits[index].changeNumber,
'treatments': map(mockSplits.splits[index].conditions[0].partitions, partition => partition.treatment),
'configs': mockSplits.splits[index].configurations || {}
'configs': mockSplits.splits[index].configurations || {},
'sets': mockSplits.splits[index].sets || [],
'defaultTreatment': mockSplits.splits[index].defaultTreatment
});

assert.equal(manager.split('non_existent'), null, 'Trying to get a manager.split() of a Split that does not exist returns null.');
Expand Down
Loading

0 comments on commit 400d93c

Please sign in to comment.