Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d08d18f
[FME-4362] update sdk version and add support for evaluation options
ZamoraEmmanuel Jul 17, 2025
f036858
Remove console log
ZamoraEmmanuel Jul 17, 2025
9845eaf
Bump on-headers and morgan
dependabot[bot] Jul 17, 2025
66fd1cd
Add prerequisites property to splitview object
ZamoraEmmanuel Jul 17, 2025
949ac9c
CHANGES file and version
ZamoraEmmanuel Jul 17, 2025
aac1303
Update splitchanges and use properties validator
ZamoraEmmanuel Jul 18, 2025
7c5a814
left track properties and evaluation options validations more permisive
ZamoraEmmanuel Jul 18, 2025
f2f905c
update properties validation message
ZamoraEmmanuel Jul 18, 2025
1ee125f
Merge pull request #206 from splitio/update-main
ZamoraEmmanuel Jul 18, 2025
d339af2
Merge branch 'development' into fme-4362
ZamoraEmmanuel Jul 18, 2025
4285407
Fix typos
ZamoraEmmanuel Jul 18, 2025
c1ef592
Merge pull request #204 from splitio/fme-4362
ZamoraEmmanuel Jul 18, 2025
d6caa68
[FME-7164] update swagger api-docs
ZamoraEmmanuel Jul 18, 2025
e8b24b6
[FME-4363] Use properties param instead of options
ZamoraEmmanuel Jul 22, 2025
47dc1b7
Typos
EmilianoSanchez Jul 22, 2025
ebafc6a
Merge pull request #208 from splitio/fme-4363
ZamoraEmmanuel Jul 22, 2025
c8caac3
Merge branch 'development' into dependabot/npm_and_yarn/multi-1083d179d6
ZamoraEmmanuel Jul 22, 2025
ea3b369
Merge pull request #205 from splitio/dependabot/npm_and_yarn/multi-10…
ZamoraEmmanuel Jul 22, 2025
566d193
Prepare release
ZamoraEmmanuel Jul 23, 2025
578089c
Update alpine 3.22
ZamoraEmmanuel Jul 23, 2025
d7efb7e
Fix changes
ZamoraEmmanuel Jul 23, 2025
a46bf19
Merge pull request #210 from splitio/prepare-release
ZamoraEmmanuel Jul 23, 2025
3837dbd
Fix openapi properties
ZamoraEmmanuel Jul 23, 2025
41424fb
Merge pull request #212 from splitio/fix-openapi
ZamoraEmmanuel Jul 23, 2025
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
11 changes: 10 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
2.8.0 (Jul 23, 2025)
- Updated base image to node:24.3.0-alpine3.22
- Updated @splitsoftware/splitio package to version 11.4.1 that includes:
- Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK.
- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules.
- Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs.
- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on `SplitView` type objects. Read more in our docs.


2.7.2 (Jul 4, 2025)
- Updated base image to node:24.3.0-alpine3.21

2.7.1 (Jun 25, 2025)
- Fixed OpenAPI spec fot /manager/splits
- Fixed OpenAPI spec for /manager/splits
- Updated base image to node:24.2.0-alpine3.22

2.7.0 (Dec 20, 2024)
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Builder stage
FROM node:24.3.0-alpine3.21 AS builder
FROM node:24.3.0-alpine3.22 AS builder

WORKDIR /usr/src/split-evaluator

Expand All @@ -8,7 +8,7 @@ COPY package.json package-lock.json ./
RUN npm install --only=production

# Runner stage
FROM node:24.3.0-alpine3.21 AS runner
FROM node:24.3.0-alpine3.22 AS runner

WORKDIR /usr/src/split-evaluator

Expand Down
34 changes: 34 additions & 0 deletions client/__tests__/allTreatments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,38 @@ describe('get-all-treatments', () => {
.set('Authorization', 'test');
expectOkAllTreatments(response, 200, expected, 2);
});

test('should be 200 if properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-all-treatments?keys=[{"matchingKey":"test","trafficType":"localhost"}]&properties={"package":"premium","admin":true,"discount":50}')
.set('Authorization', 'test');
expect(response.status).toBe(200);
});

test('should be 200 if properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-all-treatments?keys=[{"matchingKey":"test","trafficType":"localhost"}]')
.send({
properties: { package: 'premium', admin: true, discount: 50 },
})
.set('Authorization', 'test');
expect(response.status).toBe(200);
});

test('should be 200 if properties is invalid (GET)', async () => {
const response = await request(app)
.get('/client/get-all-treatments?keys=[{"matchingKey":"test","trafficType":"localhost"}]&properties={"foo": {"bar": 1}}')
.set('Authorization', 'test');
expect(response.status).toBe(200);
});

test('should be 200 if properties is invalid (POST)', async () => {
const response = await request(app)
.post('/client/get-all-treatments?keys=[{"matchingKey":"test","trafficType":"localhost"}]')
.send({
properties: { foo: { bar: 1 } },
})
.set('Authorization', 'test');
expect(response.status).toBe(200);
});
});
34 changes: 34 additions & 0 deletions client/__tests__/allTreatmentsWithConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,38 @@ describe('get-all-treatments-with-config', () => {
.set('Authorization', 'test');
expectOkAllTreatments(response, 200, expected, 2);
});

test('should be 200 if properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-all-treatments-with-config?keys=[{"matchingKey":"test","trafficType":"localhost"}]&properties={"package":"premium","admin":true,"discount":50}')
.set('Authorization', 'test');
expect(response.status).toBe(200);
});

test('should be 200 if properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-all-treatments-with-config?keys=[{"matchingKey":"test","trafficType":"localhost"}]')
.send({
properties: { package: 'premium', admin: true, discount: 50 },
})
.set('Authorization', 'test');
expect(response.status).toBe(200);
});

test('should be 200 if properties is invalid (GET)', async () => {
const response = await request(app)
.get('/client/get-all-treatments-with-config?keys=[{"matchingKey":"test","trafficType":"localhost"}]&properties={"foo": {"bar": 1}}')
.set('Authorization', 'test');
expect(response.status).toBe(200);
});

test('should be 200 if properties is invalid (POST)', async () => {
const response = await request(app)
.post('/client/get-all-treatments-with-config?keys=[{"matchingKey":"test","trafficType":"localhost"}]')
.send({
properties: { foo: { bar: 1 } },
})
.set('Authorization', 'test');
expect(response.status).toBe(200);
});
});
12 changes: 6 additions & 6 deletions client/__tests__/track.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ process.env.SPLIT_EVALUATOR_API_KEY = 'localhost';
const request = require('supertest');
const app = require('../../app');
const { expectError, expectErrorContaining, getLongKey } = require('../../utils/testWrapper');
const { PROPERTIES_WARNING } = require('../../utils/constants');

describe('track', () => {
// Testing authorization
Expand Down Expand Up @@ -143,22 +144,21 @@ describe('track', () => {
expectErrorContaining(response, 400, expected);
});

test('should be 400 if properties is invalid', async () => {
const expected = [
'properties must be a plain object.'
];
test('should be 200 if properties is invalid', async () => {
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
const response = await request(app)
.get('/client/track?key=my-key&event-type=my-event&traffic-type=my-traffic&value=1&properties=lalala')
.set('Authorization', 'test');
expectErrorContaining(response, 400, expected);
expect(response.statusCode).toBe(200);
expect(logSpy).toHaveBeenCalledWith(PROPERTIES_WARNING);
logSpy.mockRestore();
});

test('should be 400 if there are multiple errors in every input', async () => {
const expected = [
'key too long, key must be 250 characters or less.',
'you passed "@!test", event-type must adhere to the regular expression /^[a-zA-Z0-9][-_.:a-zA-Z0-9]{0,79}$/g. This means an event_type must be alphanumeric, cannot be more than 80 characters long, and can only include a dash, underscore, period, or colon as separators of alphanumeric characters.',
'you passed an empty traffic-type, traffic-type must be a non-empty string.',
'properties must be a plain object.',
'value must be null or number.'
];
const key = getLongKey();
Expand Down
34 changes: 34 additions & 0 deletions client/__tests__/treatment.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,38 @@ describe('get-treatment', () => {
.set('Authorization', 'test');
expectOk(response, 200, 'control', 'nonexistant-experiment');
});

test('should be 200 if properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatment?key=test&split-name=my-experiment&properties={"package":"premium","admin":true,"discount":50}')
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment');
});

test('should be 200 if properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatment?key=test&split-name=my-experiment')
.send({
properties: { package: 'premium', admin: true, discount: 50 },
})
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment');
});

test('should be 200 if properties is invalid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatment?key=test&split-name=my-experiment&properties={"foo": {"bar": 1}}')
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment');
});

test('should be 200 if properties is invalid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatment?key=test&split-name=my-experiment')
.send({
properties: { foo: { bar: 1 } },
})
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment');
});
});
34 changes: 34 additions & 0 deletions client/__tests__/treatmentWithConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,38 @@ describe('get-treatment-with-config', () => {
.set('Authorization', 'test');
expectOk(response, 200, 'control', 'nonexistant-experiment', null);
});

test('should be 200 if properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatment-with-config?key=test&split-name=my-experiment&properties={"package":"premium","admin":true,"discount":50}')
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment', '{"desc" : "this applies only to ON treatment"}');
});

test('should be 200 if properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatment-with-config?key=test&split-name=my-experiment')
.send({
properties: { package: 'premium', admin: true, discount: 50 },
})
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment', '{"desc" : "this applies only to ON treatment"}');
});

test('should be 200 if properties is invalid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatment-with-config?key=test&split-name=my-experiment&properties={"foo": {"bar": 1}}')
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment', '{"desc" : "this applies only to ON treatment"}');
});

test('should be 200 if properties is invalid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatment-with-config?key=test&split-name=my-experiment')
.send({
properties: { foo: { bar: 1 } },
})
.set('Authorization', 'test');
expectOk(response, 200, 'on', 'my-experiment', '{"desc" : "this applies only to ON treatment"}');
});
});
50 changes: 50 additions & 0 deletions client/__tests__/treatments.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,4 +267,54 @@ describe('get-treatments', () => {
},
}, 3);
});

test('should be 200 if properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatments?key=test&split-names=my-experiment&properties={"package":"premium","admin":true,"discount":50}')
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
},
}, 1);
});

test('should be 200 if properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatments?key=test&split-names=my-experiment')
.send({
properties: { package: 'premium', admin: true, discount: 50 },
})
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
},
}, 1);
});

test('should be 200 if properties is invalid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatments?key=test&split-names=my-experiment&properties={"foo": {"bar": 1}}')
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
},
}, 1);
});

test('should be 200 if properties is invalid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatments?key=test&split-names=my-experiment')
.send({
properties: { foo: { bar: 1 } },
})
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
},
}, 1);
});
});
36 changes: 35 additions & 1 deletion client/__tests__/treatmentsByFlagSets.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { expectedGreenResults, expectedPurpleResults, expectedPinkResults } = req
jest.mock('node-fetch', () => {
return jest.fn().mockImplementation((url) => {

const sdkUrl = 'https://sdk.test.io/api/splitChanges?s=1.1&since=-1';
const sdkUrl = 'https://sdk.test.io/api/splitChanges?s=1.3&since=-1';
const splitChange2 = require('../../utils/mocks/splitchanges.since.-1.till.1602796638344.json');
if (url.startsWith(sdkUrl)) return Promise.resolve({ status: 200, json: () => (splitChange2), ok: true });

Expand Down Expand Up @@ -273,4 +273,38 @@ describe('get-treatments-by-sets', () => {
.set('Authorization', 'key_pink');
expectOkMultipleResults(response, 200, expectedPinkResults, 5);
});

test('should be 200 if properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatments-by-sets?key=test&flag-sets=set_green&properties={"package":"premium","admin":true,"discount":50}')
.set('Authorization', 'key_green');
expect(response.status).toBe(200);
});

test('should be 200 if properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatments-by-sets?key=test&flag-sets=set_green')
.send({
properties: { package: 'premium', admin: true, discount: 50 },
})
.set('Authorization', 'key_green');
expect(response.status).toBe(200);
});

test('should be 200 if properties is invalid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatments-by-sets?key=test&flag-sets=set_green&properties={"foo": {"bar": 1}}')
.set('Authorization', 'key_green');
expect(response.status).toBe(200);
});

test('should be 200 if properties is invalid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatments-by-sets?key=test&flag-sets=set_green')
.send({
properties: { foo: { bar: 1 } },
})
.set('Authorization', 'key_green');
expect(response.status).toBe(200);
});
});
54 changes: 54 additions & 0 deletions client/__tests__/treatmentsWithConfig.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,58 @@ describe('get-treatments-with-config', () => {
},
}, 3);
});

test('should be 200 if properties is valid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatments-with-config?key=test&split-names=my-experiment&properties={"package":"premium","admin":true,"discount":50}')
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
config: '{"desc" : "this applies only to ON treatment"}',
},
}, 1);
});

test('should be 200 if properties is valid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatments-with-config?key=test&split-names=my-experiment')
.send({
properties: { package: 'premium', admin: true, discount: 50 },
})
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
config: '{"desc" : "this applies only to ON treatment"}',
},
}, 1);
});

test('should be 200 if properties is invalid (GET)', async () => {
const response = await request(app)
.get('/client/get-treatments-with-config?key=test&split-names=my-experiment&properties={"foo": {"bar": 1}}')
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
config: '{"desc" : "this applies only to ON treatment"}',
},
}, 1);
});

test('should be 200 if properties is invalid (POST)', async () => {
const response = await request(app)
.post('/client/get-treatments-with-config?key=test&split-names=my-experiment')
.send({
properties: { foo: { bar: 1 } },
})
.set('Authorization', 'test');
expectOkMultipleResults(response, 200, {
'my-experiment': {
treatment: 'on',
config: '{"desc" : "this applies only to ON treatment"}',
},
}, 1);
});
});
Loading