Skip to content

Commit

Permalink
Merge pull request #804 from splitio/development
Browse files Browse the repository at this point in the history
Release v1.26.0
  • Loading branch information
EmilianoSanchez committed May 6, 2024
2 parents 127c3c3 + 0dfd83a commit e06c342
Show file tree
Hide file tree
Showing 59 changed files with 1,091 additions and 345 deletions.
28 changes: 17 additions & 11 deletions CHANGES.txt
@@ -1,3 +1,9 @@
10.26.0 (May 6, 2024)
- Updated @splitsoftware/splitio-commons package to version 1.14.0 that includes minor updates:
- Added support for targeting rules based on semantic versions (https://semver.org/).
- Added special impression label "targeting rule type unsupported by sdk" when the matcher type is not supported by the SDK, which returns 'control' treatment.
- Updated Split API client to include the flags spec version query parameter for the `splitChanges` and `auth` endpoints.

10.25.2 (March 26, 2024)
- Updated some transitive dependencies for vulnerability fixes.
- Bugfixing - Added tslib as an explicit dependency to avoid issues with some package managers that don't resolve it automatically as a transitive dependency from @splitsoftware/splitio-commons (Related to issue https://github.com/splitio/javascript-client/issues/795).
Expand Down Expand Up @@ -107,7 +113,7 @@
- Bugfixing - Fixed an issue with `connectionTimeout` options params of Redis storage, that was being ignored and not passed down to the underlying ioredis client.
- Bugfixing - Updated the validation of some SDK configuration params to log errors and throw exceptions with clear descriptions of the invalid setup:
- If passing a non-string value to `sync.impressionsMode`, the SDK logs the error: "you passed an invalid impressionsMode config param. It should be one of the following values: 'OPTIMIZED', 'DEBUG'. Defaulting to 'OPTIMIZED'.".
- If passing 'REDIS' storage type without setting `mode` to 'consumer', the SDK logs the error: "The provided REDIS storage is invalid for this mode. It requires 'consumer' mode. Fallbacking into default MEMORY storage.".
- If passing 'REDIS' storage type without setting `mode` to 'consumer', the SDK logs the error: "The provided REDIS storage is invalid for this mode. It requires 'consumer' mode. Fallback into default MEMORY storage.".
- If passing 'consumer' mode without setting `storage.type` to 'REDIS', the SDK throws an exception with message: "A REDIS storage is required on consumer mode.".

- NOTABLE CHANGE: since version 10.18.0, the SDK has been refactored to use @splitsoftware/splitio-commons package in order to reuse core modules shared across all JavaScript-based SDKs. Most internal modules have been moved and renamed,
Expand Down Expand Up @@ -175,7 +181,7 @@
10.15.4 (Mar 17, 2021)
- Updated Streaming logic with some improvements and fixes, including:
- Updated SSE error handling.
- Extended publishers tracking to support multiregion infrastructure.
- Extended publishers tracking to support multi-region infrastructure.
- Enforced revalidation for requests stored in local caches, like proxies or browsers.
- Bugfixing - In NodeJS, fetch new segments captured due to streaming notifications.
- Updated some dependencies, including a vulnerability fix.
Expand Down Expand Up @@ -300,11 +306,11 @@
- Added Block Until Ready functionality support for consumer clients (Redis mode on Node) to make integration code work the same between modes.
- Added more Input and Usage Validation rules, including an extra label for impressions when the SDK is not ready.
- Updated the SDK Redis adapter to handle pending commands when disconnecting from the Redis server.
- Bugfixing - Clearing up readyTimeout after we don't need it anymore. It also fixes the missleading SDK_READY_TIMED_OUT error log when using Redis.
- Bugfixing - Clearing up readyTimeout after we don't need it anymore. It also fixes the misleading SDK_READY_TIMED_OUT error log when using Redis.

10.7.0 (Apr 30, 2019)
- Added Block Until Ready functionality to the manager, shared with the main client. Now you can subscribe to SDK events or use the .ready() promise from the manager as well.
- Added Dynamic Configurations support through two new methods that mimick the regular ones, changing the type of what is returned.
- Added Dynamic Configurations support through two new methods that mimic the regular ones, changing the type of what is returned.
- getTreatmentWithConfig: Same as getTreatment, but instead of a string it returns a map with treatment and config as a stringified JSON.
- getTreatmentWithConfig: Same as getTreatments, but instead of a map of string it returns a map of objects with treatment and config as a stringified JSON.
- Added configs to SplitViews returned by the manager module.
Expand All @@ -318,7 +324,7 @@

10.6.0 (Feb 12, 2019)
- BREAKING CHANGE: Updated impressions cache for Redis storage to reduce the amount of Redis operations by using a single queue (Must use Synchronizer 2.x or above with this or newer SDK versions).
- Added stricter validations to the input of the SDK api to provide better and faster feedback in case of missuse. We want our users to be able to diagnose issues sooner,
- Added stricter validations to the input of the SDK api to provide better and faster feedback in case of misuse. We want our users to be able to diagnose issues sooner,
instead of when you can't find the data you're looking for. As part of this, some error logs (just logs) will be visible even with the SDK Logger disabled.
- Updated getTreatments to have it's own latency metric for the whole operation, instead of one per each feature evaluation.
- Updated default values on configuration for NodeJS.
Expand Down Expand Up @@ -385,13 +391,13 @@
- Migrated source code to es modules.
- Localhost mode uses fewer dependencies now.
- Removed flowtype since it was not used anymore.
- Udpated to last node LTS.
- Updated to last node LTS.
- Added package-lock.json.
- Fixed eslint configuration.

9.4.0 (Jan 12, 2018)
- Adding support for client.track method, for tracking custom events.
- Adding trafficType as an optional core setting. If provided on the browser it will be binded to the client as the key.
- Adding trafficType as an optional core setting. If provided on the browser it will be bound to the client as the key.
- TypeScript declarations polishing.
- Updated SDK labels.
- Bugfixing - Shared clients (browser) were ready even if the main client was not.
Expand Down Expand Up @@ -593,10 +599,10 @@ var treatmentsMap = client.getTreatments('CUSTOMER_KEY', ['Feature_flag_1', 'Fea
const client = SplitFactory(config);

// Redis in NodeJS is async so we can use async/await syntax
const treatment = await client.getTreatment('my-feature-comming-from-localstorage');
const treatment = await client.getTreatment('my-feature-coming-from-localstorage');

// or just use the returned promise
client.getTreatment('my-feature-comming-from-localstorage').then(treatment => {
client.getTreatment('my-feature-coming-from-localstorage').then(treatment => {
// do something with the treatment
});
```
Expand Down Expand Up @@ -652,7 +658,7 @@ var treatmentsMap = client.getTreatments('CUSTOMER_KEY', ['Feature_flag_1', 'Fea

client.getTreatment('my_feature') === 'on'; // true

factory.settings.features.my_feature = 'off'; // Apply this cache programatically
factory.settings.features.my_feature = 'off'; // Apply this cache programmatically

client.getTreatment('my_feature') === 'off'; // Some time after you will be able to verify this
```
Expand Down Expand Up @@ -728,7 +734,7 @@ var treatmentsMap = client.getTreatments('CUSTOMER_KEY', ['Feature_flag_1', 'Fea
```html
<script src="//cdn.split.io/sdk/split-5.0.0.min.js"></script>
<script>
// instanciation
// instantiation
var dynamic1 = splitio({
core: {
authorizationKey: '<your-token>',
Expand Down
18 changes: 9 additions & 9 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
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio",
"version": "10.25.2",
"version": "10.26.0",
"description": "Split SDK",
"files": [
"README.md",
Expand Down Expand Up @@ -40,7 +40,7 @@
"node": ">=6"
},
"dependencies": {
"@splitsoftware/splitio-commons": "1.13.1",
"@splitsoftware/splitio-commons": "1.14.0",
"@types/google.analytics": "0.0.40",
"@types/ioredis": "^4.28.0",
"bloom-filters": "^3.0.0",
Expand Down
114 changes: 114 additions & 0 deletions src/__tests__/browserSuites/evaluations-semver.spec.js
@@ -0,0 +1,114 @@
import sinon from 'sinon';
import splitChangesMock1 from '../mocks/splitchanges.since.-1.semver.json';

import { SplitFactory } from '../../';

const listener = {
logImpression: sinon.stub()
};

const config = {
core: {
authorizationKey: '<fake-token>',
key: 'emi@split.io'
},
urls: {
sdk: 'https://sdk.evaluation-semver/api',
events: 'https://events.evaluation-semver/api'
},
sync: {
impressionsMode: 'DEBUG'
},
impressionListener: listener,
streamingEnabled: false
};

export default async function (fetchMock, assert) {

fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.1&since=-1', { status: 200, body: splitChangesMock1 });
fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.1&since=1675259356568', { status: 200, body: { splits: [], since: 1675259356568, till: 1675259356568 } });
fetchMock.getOnce(config.urls.sdk + '/mySegments/emi%40split.io', { status: 200, body: { mySegments: [] } });
fetchMock.getOnce(config.urls.sdk + '/mySegments/2nd', { status: 200, body: { mySegments: [] } });

const splitio = SplitFactory(config);
const client = splitio.client();

await client.ready();

// EQUAL_TO_SEMVER matcher
assert.equal(client.getTreatment('semver_equalto', { 'version': '1.22.9' }), 'on', 'the rule will return `on` if attribute `version` is equal to `1.22.9`');
assert.equal(client.getTreatment('semver_equalto', { 'version': '1.22.9+build' }), 'off', 'build metadata is not ignored');
assert.equal(client.getTreatment('semver_equalto', { 'version': '1.22.9-rc.0' }), 'off', 'the rule will return `off` if attribute `version` is not equal to `1.22.9`');
assert.equal(client.getTreatment('semver_equalto', { 'version': null }), 'off', 'the rule will return `off` if attribute `version` is not the expected type');
assert.equal(client.getTreatment('semver_equalto'), 'off', 'the rule will return `off` if attribute `version` is not provided');

// IN_LIST_SEMVER matcher
assert.equal(client.getTreatment('semver_inlist', { 'version': '2.1.0' }), 'on', 'the rule will return `on` if attribute `version` is in list (`1.22.9`, `2.1.0`)');
assert.equal(client.getTreatment('semver_inlist', { 'version': '1.22.9' }), 'on', 'the rule will return `on` if attribute `version` is in list (`1.22.9`, `2.1.0`)');
assert.equal(client.getTreatment('semver_inlist', { 'version': '1.22.9+build' }), 'off', 'build metadata is not ignored');
assert.equal(client.getTreatment('semver_inlist', { 'version': '1.22.9-rc.0' }), 'off', 'the rule will return `off` if attribute `version` is not in list (`1.22.9`, `2.1.0`)');
assert.equal(client.getTreatment('semver_inlist', { 'version': null }), 'off', 'the rule will return `off` if attribute `version` is not the expected type');

// GREATER_THAN_OR_EQUAL_TO_SEMVER matcher
assert.equal(client.getTreatments(['semver_greater_or_equalto'], { 'version': '1.23.9' }).semver_greater_or_equalto, 'on', 'the rule will return `on` if attribute `version` is greater than or equal to `1.22.9`');
assert.equal(client.getTreatments(['semver_greater_or_equalto'], { 'version': '1.22.9' }).semver_greater_or_equalto, 'on', 'the rule will return `on` if attribute `version` is greater than or equal to `1.22.9`');
assert.equal(client.getTreatments(['semver_greater_or_equalto'], { 'version': '1.22.9+build' }).semver_greater_or_equalto, 'on', 'build metadata is ignored');
assert.equal(client.getTreatments(['semver_greater_or_equalto'], { 'version': '1.22.9-rc.0' }).semver_greater_or_equalto, 'off', 'the rule will return `off` if attribute `version` is not greater than or equal to `1.22.9`');
assert.equal(client.getTreatments(['semver_greater_or_equalto'], { 'version': '1.21.9' }).semver_greater_or_equalto, 'off', 'the rule will return `off` if attribute `version` is not greater than or equal to `1.22.9`');
assert.equal(client.getTreatments(['semver_greater_or_equalto'], { 'version': 'invalid' }).semver_greater_or_equalto, 'off', 'the rule will return `off` if attribute `version` is an invalid semver value');

// LESS_THAN_OR_EQUAL_TO_SEMVER matcher
assert.deepEqual(client.getTreatmentWithConfig('semver_less_or_equalto', { 'version': '1.22.11' }), { treatment: 'off', config: null }, 'the rule will return `off` if attribute `version` is not less than or equal to `1.22.9`');
assert.deepEqual(client.getTreatmentWithConfig('semver_less_or_equalto', { 'version': '1.22.9' }), { treatment: 'on', config: null }, 'the rule will return `on` if attribute `version` is less than or equal to `1.22.9`');
assert.deepEqual(client.getTreatmentWithConfig('semver_less_or_equalto', { 'version': '1.22.9+build' }), { treatment: 'on', config: null }, 'build metadata is ignored');
assert.deepEqual(client.getTreatmentWithConfig('semver_less_or_equalto', { 'version': '1.22.9-rc.0' }), { treatment: 'on', config: null }, 'the rule will return `on` if attribute `version` is less than or equal to `1.22.9`');
assert.deepEqual(client.getTreatmentWithConfig('semver_less_or_equalto', { 'version': '1.21.9' }), { treatment: 'on', config: null }, 'the rule will return `on` if attribute `version` is less than or equal to `1.22.9`');
assert.deepEqual(client.getTreatmentWithConfig('semver_less_or_equalto', { 'version': {} }), { treatment: 'off', config: null }, 'the rule will return `off` if attribute `version` is not the expected type');

const client2 = splitio.client('2nd');
await client2.ready();

// BETWEEN_SEMVER matcher
assert.deepEqual(client2.getTreatmentsWithConfig(['semver_between'], { 'version': '2.1.1' }).semver_between, { treatment: 'off', config: null }, 'the rule will return `off` if attribute `version` is not between `1.22.9` and `2.1.0`');
assert.deepEqual(client2.getTreatmentsWithConfig(['semver_between'], { 'version': '2.1.0+build' }).semver_between, { treatment: 'on', config: null }, 'build metadata is ignored');
assert.deepEqual(client2.getTreatmentsWithConfig(['semver_between'], { 'version': '1.25.0' }).semver_between, { treatment: 'on', config: null }, 'the rule will return `on` if attribute `version` is between `1.22.9` and `2.1.0`');
assert.deepEqual(client2.getTreatmentsWithConfig(['semver_between'], { 'version': '1.22.9' }).semver_between, { treatment: 'on', config: null }, 'the rule will return `on` if attribute `version` is between `1.22.9` and `2.1.0`');
assert.deepEqual(client2.getTreatmentsWithConfig(['semver_between'], { 'version': '1.22.9-rc.0' }).semver_between, { treatment: 'off', config: null }, 'the rule will return `off` if attribute `version` is not between `1.22.9` and `2.1.0`');
assert.deepEqual(client2.getTreatmentsWithConfig(['semver_between'], { 'version': [] }).semver_between, { treatment: 'off', config: null }, 'the rule will return `off` if attribute `version` is not the expected type');

// Evaluation of a flag with unsupported matcher
assert.equal(client2.getTreatment('flag_with_unsupported_matcher'), 'control', 'evaluation of a flag with an unsupported matcher should return control');

let POSTED_IMPRESSIONS_COUNT;

fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', (_, opts) => {

const payload = JSON.parse(opts.body);

function validateImpressionData(featureFlagName, expectedImpressionCount, expectedOnCount, expectedLabel, expectedTreatment = 'on') {
const impressions = payload.find(e => e.f === featureFlagName).i;

assert.equal(impressions.length, expectedImpressionCount, `We should have ${expectedImpressionCount} impressions for the feature flag ${featureFlagName}`);
assert.equal(impressions.filter((imp) => imp.r === expectedLabel && imp.t === expectedTreatment).length, expectedOnCount, `${expectedOnCount} impression with 'on' treatment and label ${expectedLabel}`);
}

validateImpressionData('semver_equalto', 5, 1, 'equal to semver');
validateImpressionData('semver_inlist', 5, 2, 'in list semver');
validateImpressionData('semver_greater_or_equalto', 6, 3, 'greater than or equal to semver');
validateImpressionData('semver_less_or_equalto', 6, 4, 'less than or equal to semver');
validateImpressionData('semver_between', 6, 3, 'between semver');
validateImpressionData('flag_with_unsupported_matcher', 1, 1, 'targeting rule type unsupported by sdk', 'control');

POSTED_IMPRESSIONS_COUNT = payload.reduce((acc, curr) => acc + curr.i.length, 0);

return 200;
});

await Promise.all([client.destroy(), client2.destroy()]);

setTimeout(() => {
assert.equal(listener.logImpression.callCount, POSTED_IMPRESSIONS_COUNT, 'Impression listener should be called once per each impression generated.');

assert.end();
}, 0);
}

0 comments on commit e06c342

Please sign in to comment.