Skip to content

Commit 396bfbd

Browse files
authored
Merge pull request #238 from splitio/sdks-7463
[SDKS-7463] Add new `bySet` filter
2 parents 62fe4c9 + a42693c commit 396bfbd

File tree

10 files changed

+189
-31
lines changed

10 files changed

+189
-31
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio-commons",
3-
"version": "1.9.0",
3+
"version": "1.9.1-rc.0",
44
"description": "Split Javascript SDK common components",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",

src/__tests__/mocks/fetchSpecificSplits.ts

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ const valuesExamples = [
1010
['p0', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9', 'p10', 'p11', 'p12', 'p13', 'p14', 'p15', 'p16', 'p17', 'p18', 'p19', 'p20', 'p21', 'p22', 'p23', 'p24', 'p25', 'p26', 'p27', 'p28', 'p29', 'p30', 'p31', 'p32', 'p33', 'p34', 'p35', 'p36', 'p37', 'p38', 'p39', 'p40', 'p41', 'p42', 'p43', 'p44', 'p45', 'p46', 'p47', 'p48', 'p49', 'p50'],
1111
['__ш', '__a', '%', '%25', ' __ш ', '% '], // to test that we order before encoding: '__a' < '__ш' but encodeURIComponent('__a') > encodeURIComponent('__ш')
1212
['%', '%25', '__a', '__ш'], // [7] ordered and deduplicated
13+
// flagSets examples
14+
[' set_1','set_3 ',' set_a ','set_2','set_c','set_b'], // [9] trim
15+
['set_1','set_2','set_3','set_a','set_b','set_c'], // [10] sanitized [9]
16+
['set_ 1','set _3','3set_a','_set_2','seT_c','set_B','set_1234567890_1234567890_234567890_1234567890_1234567890','set_a','set_2'], // [11] lowercase & regexp
17+
['set_2','set_a','set_b','set_c'], // [12] sanitized [11]
18+
['set_2','set_a','SET_2','set_a','set_b','set_B','set_1','set_3!'], // [13] dedupe, dedupe with case sensitive
19+
['set_1','set_2','set_a','set_b'], // [14] sanitized [13]
1320
];
1421

1522
export const splitFilters: SplitIO.SplitFilter[][] = [
@@ -41,39 +48,86 @@ export const splitFilters: SplitIO.SplitFilter[][] = [
4148
],
4249
[
4350
{ type: 'byName', values: valuesExamples[7] }
44-
]
51+
],
52+
// FlagSet filters
53+
[ // [6]
54+
{ type: 'byPrefix', values: valuesExamples[1] },
55+
{ type: 'bySet', values: valuesExamples[9] },
56+
{ type: 'byName', values: valuesExamples[1] }
57+
],
58+
[ // [7]
59+
{ type: 'bySet', values: valuesExamples[11] },
60+
{ type: 'byPrefix', values: [] },
61+
{ type: 'byName', values: valuesExamples[6] }
62+
],
63+
[ // [8]
64+
{ type: 'byPrefix', values: [] },
65+
{ type: 'byName', values: valuesExamples[6] },
66+
{ type: 'bySet', values: valuesExamples[13] }
67+
],
4568
];
4669

4770
// each entry corresponds to the queryString or exception message of each splitFilters entry
4871
export const queryStrings = [
49-
'&names=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc',
50-
'&prefixes=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc',
51-
'&names=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc&prefixes=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc',
52-
"400 unique values can be specified at most for 'byName' filter. You passed 401. Please consider reducing the amount or using other filter.",
53-
"50 unique values can be specified at most for 'byPrefix' filter. You passed 51. Please consider reducing the amount or using other filter.",
54-
'&names=%25,%2525,__a,__%D1%88',
72+
'&names=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc', // [0]
73+
'&prefixes=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc', // [1]
74+
'&names=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc&prefixes=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc', // [2]
75+
"400 unique values can be specified at most for 'byName' filter. You passed 401. Please consider reducing the amount or using other filter.", // [3]
76+
"50 unique values can be specified at most for 'byPrefix' filter. You passed 51. Please consider reducing the amount or using other filter.", // [4]
77+
'&names=%25,%2525,__a,__%D1%88', // [5]
78+
// FlagSet filters
79+
'&sets=set_1,set_2,set_3,set_a,set_b,set_c', // [6]
80+
'&sets=set_2,set_a,set_b,set_c', // [7]
81+
'&sets=set_1,set_2,set_a,set_b', // [8]
5582
];
5683

5784
// each entry corresponds to a `groupedFilter` object returned by `validateSplitFilter` for each `splitFilters` input.
5885
// `groupedFilter` contains valid, unique and ordered values per filter type.
5986
// An `undefined` value means that `validateSplitFilter` throws an exception which message value is at `queryStrings`.
6087
export const groupedFilters = [
61-
{
88+
{ // [0]
89+
bySet: [],
6290
byName: valuesExamples[2],
6391
byPrefix: []
6492
},
65-
{
93+
{ // [1]
94+
bySet: [],
6695
byName: [],
6796
byPrefix: valuesExamples[2]
6897
},
69-
{
98+
{ // [2]
99+
bySet: [],
70100
byName: valuesExamples[2],
71101
byPrefix: valuesExamples[2]
72102
},
73-
undefined,
74-
undefined,
75-
{
103+
undefined, // [3]
104+
undefined, // [4]
105+
{ // [5]
106+
bySet: [],
76107
byName: valuesExamples[8],
77108
byPrefix: []
78-
}
109+
},
110+
// FlagSet filters
111+
{ // [6]
112+
byName: [],
113+
bySet: valuesExamples[10],
114+
byPrefix: []
115+
},
116+
{ // [7]
117+
byName: [],
118+
bySet: valuesExamples[12],
119+
byPrefix: []
120+
},
121+
{ // [8]
122+
byName: [],
123+
bySet: valuesExamples[14],
124+
byPrefix: []
125+
},
126+
];
127+
128+
export const flagSetValidFilters = [
129+
undefined, undefined, undefined, undefined, undefined, undefined,
130+
[{ type: 'bySet', values: valuesExamples[9] }],
131+
[{ type: 'bySet', values: valuesExamples[11] }],
132+
[{ type: 'bySet', values: valuesExamples[13] }],
79133
];

src/logger/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ export const WARN_SPLITS_FILTER_EMPTY = 221;
9797
export const WARN_SDK_KEY = 222;
9898
export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223;
9999
export const STREAMING_PARSING_SPLIT_UPDATE = 224;
100+
export const WARN_SPLITS_FILTER_NAME_AND_SET = 225;
101+
export const WARN_SPLITS_FILTER_INVALID_SET = 226;
102+
export const WARN_SPLITS_FILTER_LOWERCASE_SET = 227;
100103

101104
export const ERROR_ENGINE_COMBINER_IFELSEIF = 300;
102105
export const ERROR_LOGLEVEL_INVALID = 301;

src/logger/messages/warn.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ export const codesWarn: [number, string][] = codesError.concat([
2727
// initialization / settings validation
2828
[c.WARN_INTEGRATION_INVALID, c.LOG_PREFIX_SETTINGS+': %s integration item(s) at settings is invalid. %s'],
2929
[c.WARN_SPLITS_FILTER_IGNORED, c.LOG_PREFIX_SETTINGS+': feature flag filters have been configured but will have no effect if mode is not "%s", since synchronization is being deferred to an external tool.'],
30-
[c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS+': feature flag filter at position %s is invalid. It must be an object with a valid filter type ("byName" or "byPrefix") and a list of "values".'],
30+
[c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS+': feature flag filter at position %s is invalid. It must be an object with a valid filter type ("bySet", "byName" or "byPrefix") and a list of "values".'],
3131
[c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS+': feature flag filter configuration must be a non-empty array of filter objects.'],
3232
[c.WARN_SDK_KEY, c.LOG_PREFIX_SETTINGS+': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'],
3333

3434
[c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'],
3535
[c.STREAMING_PARSING_SPLIT_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching SplitChanges due to an error processing SPLIT_UPDATE notification: %s'],
36+
[c.WARN_SPLITS_FILTER_NAME_AND_SET, c.LOG_PREFIX_SETTINGS+': names and sets filter cannot be used at the same time. The sdk will proceed using sets filter.'],
37+
[c.WARN_SPLITS_FILTER_INVALID_SET, c.LOG_PREFIX_SETTINGS+': you passed %s, flag set must adhere to the regular expressions %s. This means a flag set must start with a letter, be in lowercase, alphanumeric and have a max length of 50 characteres. %s was discarded.'],
38+
[c.WARN_SPLITS_FILTER_LOWERCASE_SET, c.LOG_PREFIX_SETTINGS+': flag set %s should be all lowercase - converting string to lowercase.'],
3639
]);

src/storages/inLocalStorage/SplitsCacheInLocal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
2121
* @param {number | undefined} expirationTimestamp
2222
* @param {ISplitFiltersValidation} splitFiltersValidation
2323
*/
24-
constructor(private readonly log: ILogger, keys: KeyBuilderCS, expirationTimestamp?: number, splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { byName: [], byPrefix: [] }, validFilters: [] }) {
24+
constructor(private readonly log: ILogger, keys: KeyBuilderCS, expirationTimestamp?: number, splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }) {
2525
super();
2626
this.keys = keys;
2727
this.splitFiltersValidation = splitFiltersValidation;

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ export namespace SplitIO {
709709
* SplitFilter type.
710710
* @typedef {string} SplitFilterType
711711
*/
712-
export type SplitFilterType = 'byName' | 'byPrefix';
712+
export type SplitFilterType = 'byName' | 'byPrefix' | 'bySet';
713713
/**
714714
* Defines a feature flag filter, described by a type and list of values.
715715
*/

src/utils/settingsValidation/__tests__/settings.mocks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export const fullSettings: ISettings = {
7777
__splitFiltersValidation: {
7878
validFilters: [],
7979
queryString: null,
80-
groupedFilters: { byName: [], byPrefix: [] }
80+
groupedFilters: { bySet: [], byName: [], byPrefix: [] }
8181
},
8282
enabled: true
8383
},

src/utils/settingsValidation/__tests__/splitFilters.spec.ts

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,31 @@ import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock';
33
import { STANDALONE_MODE, CONSUMER_MODE } from '../../constants';
44

55
// Split filter and QueryStrings examples
6-
import { splitFilters, queryStrings, groupedFilters } from '../../../__tests__/mocks/fetchSpecificSplits';
6+
import { splitFilters, queryStrings, groupedFilters, flagSetValidFilters } from '../../../__tests__/mocks/fetchSpecificSplits';
77

88
// Test target
99
import { validateSplitFilters } from '../splitFilters';
10-
import { SETTINGS_SPLITS_FILTER, ERROR_INVALID, ERROR_EMPTY_ARRAY, WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_INVALID, WARN_SPLITS_FILTER_EMPTY } from '../../../logger/constants';
10+
import { SETTINGS_SPLITS_FILTER, ERROR_INVALID, ERROR_EMPTY_ARRAY, WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_INVALID, WARN_SPLITS_FILTER_EMPTY, WARN_TRIMMING, WARN_SPLITS_FILTER_NAME_AND_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_SPLITS_FILTER_LOWERCASE_SET } from '../../../logger/constants';
1111

1212
describe('validateSplitFilters', () => {
1313

1414
const defaultOutput = {
1515
validFilters: [],
1616
queryString: null,
17-
groupedFilters: { byName: [], byPrefix: [] }
17+
groupedFilters: { bySet: [], byName: [], byPrefix: [] }
1818
};
1919

20+
const getOutput = (testIndex: number) => {
21+
return {
22+
// @ts-ignore
23+
validFilters: [...flagSetValidFilters[testIndex]],
24+
queryString: queryStrings[testIndex],
25+
groupedFilters: groupedFilters[testIndex]
26+
};
27+
};
28+
29+
const regexp = /^[a-z][_a-z0-9]{0,49}$/;
30+
2031
afterEach(() => { loggerMock.mockClear(); });
2132

2233
test('Returns default output with empty values if `splitFilters` is an invalid object or `mode` is not \'standalone\'', () => {
@@ -39,13 +50,14 @@ describe('validateSplitFilters', () => {
3950
test('Returns object with null queryString, if `splitFilters` contains invalid filters or contains filters with no values or invalid values', () => {
4051

4152
const splitFilters: any[] = [
53+
{ type: 'bySet', values: [] },
4254
{ type: 'byName', values: [] },
4355
{ type: 'byName', values: [] },
4456
{ type: 'byPrefix', values: [] }];
4557
const output = {
4658
validFilters: [...splitFilters],
4759
queryString: null,
48-
groupedFilters: { byName: [], byPrefix: [] }
60+
groupedFilters: { bySet: [], byName: [], byPrefix: [] }
4961
};
5062
expect(validateSplitFilters(loggerMock, splitFilters, STANDALONE_MODE)).toEqual(output); // filters without values
5163
expect(loggerMock.debug).toBeCalledWith(SETTINGS_SPLITS_FILTER, [null]);
@@ -60,9 +72,9 @@ describe('validateSplitFilters', () => {
6072
expect(validateSplitFilters(loggerMock, splitFilters, STANDALONE_MODE)).toEqual(output); // some filters are invalid
6173
expect(loggerMock.debug.mock.calls).toEqual([[SETTINGS_SPLITS_FILTER, [null]]]);
6274
expect(loggerMock.warn.mock.calls).toEqual([
63-
[WARN_SPLITS_FILTER_INVALID, [3]], // invalid value of `type` property
64-
[WARN_SPLITS_FILTER_INVALID, [4]], // invalid type of `values` property
65-
[WARN_SPLITS_FILTER_INVALID, [5]] // invalid type of `type` property
75+
[WARN_SPLITS_FILTER_INVALID, [4]], // invalid value of `type` property
76+
[WARN_SPLITS_FILTER_INVALID, [5]], // invalid type of `values` property
77+
[WARN_SPLITS_FILTER_INVALID, [6]] // invalid type of `type` property
6678
]);
6779

6880
expect(loggerMock.error.mock.calls).toEqual([
@@ -73,7 +85,7 @@ describe('validateSplitFilters', () => {
7385

7486
test('Returns object with a queryString, if `splitFilters` contains at least a valid `byName` or `byPrefix` filter with at least a valid value', () => {
7587

76-
for (let i = 0; i < splitFilters.length; i++) {
88+
for (let i = 0; i < 6; i++) {
7789

7890
if (groupedFilters[i]) { // tests where validateSplitFilters executes normally
7991
const output = {
@@ -90,4 +102,30 @@ describe('validateSplitFilters', () => {
90102
}
91103
});
92104

105+
test('Validates flag set filters', () => {
106+
// extra spaces trimmed and sorted query output
107+
expect(validateSplitFilters(loggerMock, splitFilters[6], STANDALONE_MODE)).toEqual(getOutput(6)); // trim & sort
108+
expect(loggerMock.warn.mock.calls[0]).toEqual([WARN_TRIMMING, ['settings', 'bySet filter value', ' set_1']]);
109+
expect(loggerMock.warn.mock.calls[1]).toEqual([WARN_TRIMMING, ['settings', 'bySet filter value', 'set_3 ']]);
110+
expect(loggerMock.warn.mock.calls[2]).toEqual([WARN_TRIMMING, ['settings', 'bySet filter value', ' set_a ']]);
111+
expect(loggerMock.warn.mock.calls[3]).toEqual([WARN_SPLITS_FILTER_NAME_AND_SET]);
112+
113+
expect(validateSplitFilters(loggerMock, splitFilters[7], STANDALONE_MODE)).toEqual(getOutput(7)); // lowercase and regexp
114+
expect(loggerMock.warn.mock.calls[4]).toEqual([WARN_SPLITS_FILTER_LOWERCASE_SET, ['seT_c']]); // lowercase
115+
expect(loggerMock.warn.mock.calls[5]).toEqual([WARN_SPLITS_FILTER_LOWERCASE_SET, ['set_B']]); // lowercase
116+
expect(loggerMock.warn.mock.calls[6]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set_ 1', regexp, 'set_ 1']]); // empty spaces
117+
expect(loggerMock.warn.mock.calls[7]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set _3', regexp, 'set _3']]); // empty spaces
118+
expect(loggerMock.warn.mock.calls[8]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['3set_a', regexp, '3set_a']]); // start with a letter
119+
expect(loggerMock.warn.mock.calls[9]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['_set_2', regexp, '_set_2']]); // start with a letter
120+
expect(loggerMock.warn.mock.calls[10]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set_1234567890_1234567890_234567890_1234567890_1234567890', regexp, 'set_1234567890_1234567890_234567890_1234567890_1234567890']]); // max of 50 characters
121+
expect(loggerMock.warn.mock.calls[11]).toEqual([WARN_SPLITS_FILTER_NAME_AND_SET]);
122+
123+
expect(validateSplitFilters(loggerMock, splitFilters[8], STANDALONE_MODE)).toEqual(getOutput(8)); // lowercase and dedupe
124+
expect(loggerMock.warn.mock.calls[12]).toEqual([WARN_SPLITS_FILTER_LOWERCASE_SET, ['SET_2']]); // lowercase
125+
expect(loggerMock.warn.mock.calls[13]).toEqual([WARN_SPLITS_FILTER_LOWERCASE_SET, ['set_B']]); // lowercase
126+
expect(loggerMock.warn.mock.calls[14]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set_3!', regexp, 'set_3!']]); // special character
127+
expect(loggerMock.warn.mock.calls[15]).toEqual([WARN_SPLITS_FILTER_NAME_AND_SET]);
128+
129+
expect(loggerMock.warn.mock.calls.length).toEqual(16);
130+
});
93131
});

0 commit comments

Comments
 (0)