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

Flag sets #771

Merged
merged 57 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
33cdcab
[SDKS-7563] stabilize integration tests for flag sets
Sep 28, 2023
a2fa07a
Update commons rc version
Sep 28, 2023
b7b8434
Improve ready from cache tests
Oct 2, 2023
079e733
Merge pull request #762 from splitio/sdks-7563
emmaz90 Oct 2, 2023
c510e36
[SDKS-7564] Flag sets integration tests
Oct 2, 2023
b9fab93
[SDKS-7567] Add type definitions
Oct 9, 2023
933bcf2
Fix typo in splitChanges file
Oct 10, 2023
03bff12
Fix flag sets typo
Oct 10, 2023
c30720f
Merge branch 'sdks-7564' of https://github.com/splitio/javascript-cli…
Oct 10, 2023
602fc7f
Integration tests improvements
Oct 18, 2023
691f92f
Merge branch 'sdks-7564' of https://github.com/splitio/javascript-cli…
Oct 18, 2023
e28b1fd
Merge branch 'development' of https://github.com/splitio/javascript-c…
Oct 20, 2023
7f95c92
Update commons version
Oct 20, 2023
ae4c2b5
Merge branch 'sdks-7437' into sdks-7564
Oct 20, 2023
97b4da9
Fix missing post
Oct 20, 2023
936dc8b
Merge branch 'sdks-7564' of https://github.com/splitio/javascript-cli…
Oct 20, 2023
66ddb34
Merge pull request #763 from splitio/sdks-7564
emmaz90 Oct 20, 2023
a3045ec
Merge pull request #764 from splitio/sdks-7567
emmaz90 Oct 20, 2023
1912e9f
Update rc version
Oct 24, 2023
fffc43d
modify git ci to upload stage assets
Oct 24, 2023
e522327
Add async type definitions and ts-tests
Oct 26, 2023
7686500
[SDKS-7657] update commons version & update changes file
Nov 1, 2023
8cda413
fix ci
Nov 1, 2023
18712fb
Fix tests and update commons rc version
Nov 2, 2023
4c1deff
fix lint
Nov 2, 2023
da0f440
Fix rc version
Nov 2, 2023
51cb6c9
fix type definition
Nov 2, 2023
3585b64
fix ts-tests
Nov 3, 2023
042ce05
Merge pull request #773 from splitio/sdks-7657
emmaz90 Nov 3, 2023
8e90ebc
update changes file
Nov 3, 2023
101d504
Merge branch 'development' of https://github.com/splitio/javascript-c…
Nov 3, 2023
83e5705
update rc version
Nov 3, 2023
ebb98d2
fix lint
Nov 3, 2023
c45244a
New mock for flagsets
Nov 3, 2023
d13fdcc
Adding redis tests part1 for flagsets, this wont work without commons
Nov 3, 2023
bc22063
add test to validate that NodeJS SDK times out when a client-side SDK…
EmilianoSanchez Nov 3, 2023
eb43b58
add changelog entry
EmilianoSanchez Nov 3, 2023
bbad0a4
validate ready promise rejection
EmilianoSanchez Nov 3, 2023
f9728ca
Merge pull request #775 from splitio/fix_sdk_key_validation
EmilianoSanchez Nov 3, 2023
ddeef83
add tests with configs and telemetry latency validations
Nov 3, 2023
e611cff
merging changes
Nov 3, 2023
65572e9
Release v10.24.0-beta
Nov 8, 2023
8040348
fix changes file
Nov 8, 2023
886a31c
Merge pull request #776 from splitio/beta
emmaz90 Nov 8, 2023
e58c8f9
Update E2E tests to validate flag sets support in consumer mode
EmilianoSanchez Nov 27, 2023
12a0741
Merge branch 'fix_sdk_key_validation' into sdks-7437
EmilianoSanchez Nov 27, 2023
98c7c93
Merge branch 'sdks-7437' into SDKS-7796-flagsets_in_redis_storage
EmilianoSanchez Nov 27, 2023
0923a26
Merge branch 'sdks-7437' into sdks-7658
EmilianoSanchez Nov 27, 2023
3cd341f
Add manager method asserts for consumer mode with Redis
EmilianoSanchez Nov 29, 2023
9fee387
Updates
EmilianoSanchez Nov 30, 2023
7c4eb1b
Revert validation of flag sets evaluation, to merge with branch sdks-…
EmilianoSanchez Nov 30, 2023
36e51a5
Merge branch 'SDKS-7796-flagsets_in_redis_storage' into sdks-7658
EmilianoSanchez Nov 30, 2023
550261f
Fix tests and prepare rc
EmilianoSanchez Nov 30, 2023
6118ecf
rc with log fixes
EmilianoSanchez Dec 1, 2023
e659032
Rollback ci-cd config
EmilianoSanchez Dec 4, 2023
e039a7c
Merge pull request #774 from splitio/sdks-7658
EmilianoSanchez Dec 4, 2023
3e5741a
Merge branch 'development' into sdks-7437
EmilianoSanchez Dec 4, 2023
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
14 changes: 12 additions & 2 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
10.24.0 (October XX, 2023)
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.10.0 that includes vulnerability fixes, and adds the `defaultTreatment` property to the `SplitView` object.
- 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).
Expand Down
30 changes: 15 additions & 15 deletions package-lock.json

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

4 changes: 2 additions & 2 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-rc.1",
"description": "Split SDK",
"files": [
"README.md",
Expand Down Expand Up @@ -40,7 +40,7 @@
"node": ">=6"
},
"dependencies": {
"@splitsoftware/splitio-commons": "1.10.0",
"@splitsoftware/splitio-commons": "1.12.1-rc.4",
"@types/google.analytics": "0.0.40",
"@types/ioredis": "^4.28.0",
"bloom-filters": "^3.0.0",
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');

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

Expand Down
Loading
Loading