Skip to content

Commit 907adcc

Browse files
Merge pull request #490 from splitio/sdk-configs-endpoint-updates
[SDK Configs] `/configs` endpoint updates
2 parents 9b7d541 + 57992f2 commit 907adcc

5 files changed

Lines changed: 177 additions & 35 deletions

File tree

src/services/__tests__/splitApi.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ describe('splitApi', () => {
7878

7979
function expectedConfigsUrl(since: number, till: number, usesFilter: boolean, settings: ISettings, rbSince?: number) {
8080
const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
81-
return `sdk/configs?${settings.sync.flagSpecVersion ? `s=${settings.sync.flagSpecVersion}&` : ''}since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${usesFilter ? filterQueryString : ''}${till ? '&till=' + till : ''}`;
81+
return `sdk/v1/configs?${settings.sync.flagSpecVersion ? `s=${settings.sync.flagSpecVersion}&` : ''}since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${usesFilter ? filterQueryString : ''}${till ? '&till=' + till : ''}`;
8282
}
8383
});
8484

src/services/splitApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function splitApiFactory(
6262
},
6363

6464
fetchConfigs(since: number, noCache?: boolean, till?: number, rbSince?: number) {
65-
const url = `${urls.sdk}/configs?${settings.sync.flagSpecVersion ? `s=${settings.sync.flagSpecVersion}&` : ''}since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
65+
const url = `${urls.sdk}/v1/configs?${settings.sync.flagSpecVersion ? `s=${settings.sync.flagSpecVersion}&` : ''}since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
6666
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(CONFIGS));
6767
},
6868

src/sync/polling/fetchers/__tests__/configsFetcher.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ const INPUT: IConfigsResponse = {
55
since: 100,
66
till: 200,
77
updated: [{
8-
name: 'SomeConfig1',
8+
identifier: 'SomeConfig1',
99
variants: [{ name: 'v1', definition: { prop1: true, prop2: 123 } }, { name: 'v2', definition: { prop1: false, prop2: 456 } }],
1010
changeNumber: 0,
11-
targeting: { default: 'v2', conditions: [{ partitions: [{ variant: 'v1', size: 100 }], label: 'main condition', matchers: [{ type: 'IS_EQUAL_TO', data: { type: 'NUMBER', number: 42 }, attribute: 'age' }, { type: 'WHITELIST', data: { strings: ['a', 'b', 'c'] }, attribute: 'favoriteCharacter' }] }] }
11+
targeting: { default: 'v2', conditions: [{ partitions: [{ variant: 'v1', size: 100 }], label: 'main condition', matchers: [{ type: 'EQUAL_TO', data: { type: 'NUMBER', number: 42 }, attribute: 'age' }, { type: 'WHITELIST', data: { strings: ['a', 'b', 'c'] }, attribute: 'favoriteCharacter' }] }] }
1212
}],
1313
};
1414

src/sync/polling/fetchers/configsFetcher.ts

Lines changed: 171 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,154 @@ import { IDefinitionChangesFetcher } from './types';
44
import SplitIO from '../../../../types/splitio';
55
import { ISdkFactoryContextSync } from '../../../sdkFactory/types';
66

7-
type IConfigMatcher = {
8-
type: 'IS_EQUAL_TO';
9-
data: { type: 'NUMBER'; number: number };
10-
attribute?: string;
11-
} | {
12-
type: 'WHITELIST';
7+
type IConfigMatcherDataType = 'DATETIME' | 'NUMBER'
8+
9+
interface IConfigMatcherBase {
10+
type: string;
11+
attribute?: string | null;
12+
data?:
13+
{ type?: IConfigMatcherDataType; number: number } |
14+
{ type?: IConfigMatcherDataType; start: number; end: number } |
15+
{ strings: string[] } |
16+
{ name: string } |
17+
{ config: string; variants: string[] } |
18+
{ value: boolean } |
19+
{ string: string } |
20+
{ start: string; end: string }
21+
}
22+
23+
interface IAllKeysConfigMatcher extends IConfigMatcherBase {
24+
type: 'ALL_KEYS'
25+
}
26+
27+
interface IWhitelistConfigMatcher extends IConfigMatcherBase {
28+
type: 'WHITELIST',
29+
data: { strings: string[] }
30+
}
31+
32+
interface IEqualToConfigMatcher extends IConfigMatcherBase {
33+
type: 'EQUAL_TO';
34+
data: { type?: IConfigMatcherDataType; number: number };
35+
}
36+
37+
interface IGreaterThanOrEqualToConfigMatcher extends IConfigMatcherBase {
38+
type: 'GREATER_THAN_OR_EQUAL_TO';
39+
data: { type?: IConfigMatcherDataType; number: number };
40+
}
41+
42+
interface ILessThanOrEqualToConfigMatcher extends IConfigMatcherBase {
43+
type: 'LESS_THAN_OR_EQUAL_TO';
44+
data: { type?: IConfigMatcherDataType; number: number };
45+
}
46+
47+
interface IBetweenConfigMatcher extends IConfigMatcherBase {
48+
type: 'BETWEEN';
49+
data: { type?: IConfigMatcherDataType; start: number; end: number };
50+
}
51+
52+
interface IInSegmentConfigMatcher extends IConfigMatcherBase {
53+
type: 'IN_SEGMENT';
54+
data: { name: string };
55+
}
56+
57+
interface IInRBSegmentConfigMatcher extends IConfigMatcherBase {
58+
type: 'IN_RULE_BASED_SEGMENT';
59+
data: { name: string };
60+
}
61+
62+
interface IInLargeSegmentConfigMatcher extends IConfigMatcherBase {
63+
type: 'IN_LARGE_SEGMENT';
64+
data: { name: string };
65+
}
66+
67+
interface IEqualToSetConfigMatcher extends IConfigMatcherBase {
68+
type: 'EQUAL_TO_SET';
69+
data: { strings: string[] };
70+
}
71+
72+
interface IContainsAnyOfSetConfigMatcher extends IConfigMatcherBase {
73+
type: 'CONTAINS_ANY_OF_SET';
74+
data: { strings: string[] };
75+
}
76+
77+
interface IContainsAllOfSetConfigMatcher extends IConfigMatcherBase {
78+
type: 'CONTAINS_ALL_OF_SET';
79+
data: { strings: string[] };
80+
}
81+
82+
interface IPartOfSetConfigMatcher extends IConfigMatcherBase {
83+
type: 'PART_OF_SET';
84+
data: { strings: string[] };
85+
}
86+
87+
interface IStartsWithConfigMatcher extends IConfigMatcherBase {
88+
type: 'STARTS_WITH';
89+
data: { strings: string[] };
90+
}
91+
92+
interface IEndsWithConfigMatcher extends IConfigMatcherBase {
93+
type: 'ENDS_WITH';
94+
data: { strings: string[] };
95+
}
96+
97+
interface IContainsStringConfigMatcher extends IConfigMatcherBase {
98+
type: 'CONTAINS_STRING';
99+
data: { strings: string[] };
100+
}
101+
102+
interface IInListSemverConfigMatcher extends IConfigMatcherBase {
103+
type: 'IN_LIST_SEMVER';
13104
data: { strings: string[] };
14-
attribute?: string;
15105
}
16106

107+
interface IInConfigVariantConfigMatcher extends IConfigMatcherBase {
108+
type: 'IN_CONFIG_VARIANT';
109+
data: { config: string; variants: string[] };
110+
}
111+
112+
interface IEqualToBooleanConfigMatcher extends IConfigMatcherBase {
113+
type: 'EQUAL_TO_BOOLEAN';
114+
data: { value: boolean };
115+
}
116+
117+
interface IMatchesStringConfigMatcher extends IConfigMatcherBase {
118+
type: 'MATCHES_STRING';
119+
data: { string: string };
120+
}
121+
122+
interface IEqualToSemverConfigMatcher extends IConfigMatcherBase {
123+
type: 'EQUAL_TO_SEMVER';
124+
data: { string: string };
125+
}
126+
127+
interface IGreaterThanOrEqualToSemverConfigMatcher extends IConfigMatcherBase {
128+
type: 'GREATER_THAN_OR_EQUAL_TO_SEMVER';
129+
data: { string: string };
130+
}
131+
132+
interface ILessThanOrEqualToSemverConfigMatcher extends IConfigMatcherBase {
133+
type: 'LESS_THAN_OR_EQUAL_TO_SEMVER';
134+
data: { string: string };
135+
}
136+
137+
interface IBetweenSemverConfigMatcher extends IConfigMatcherBase {
138+
type: 'BETWEEN_SEMVER';
139+
data: { start: string; end: string };
140+
}
141+
142+
type IConfigMatcher = IAllKeysConfigMatcher | IInSegmentConfigMatcher | IWhitelistConfigMatcher | IEqualToConfigMatcher | IGreaterThanOrEqualToConfigMatcher |
143+
ILessThanOrEqualToConfigMatcher | IBetweenConfigMatcher | IEqualToSetConfigMatcher | IContainsAnyOfSetConfigMatcher | IContainsAllOfSetConfigMatcher | IPartOfSetConfigMatcher |
144+
IStartsWithConfigMatcher | IEndsWithConfigMatcher | IContainsStringConfigMatcher | IInConfigVariantConfigMatcher | IEqualToBooleanConfigMatcher | IMatchesStringConfigMatcher |
145+
IEqualToSemverConfigMatcher | IGreaterThanOrEqualToSemverConfigMatcher | ILessThanOrEqualToSemverConfigMatcher | IBetweenSemverConfigMatcher | IInListSemverConfigMatcher |
146+
IInLargeSegmentConfigMatcher | IInRBSegmentConfigMatcher
147+
17148
interface IConfigPartition {
18149
variant: string
19150
size: number
20151
}
21152

22153
interface IConfig {
23-
name: string;
154+
identifier: string;
24155
variants: Array<{
25156
name: string;
26157
definition: SplitIO.JsonObject;
@@ -37,6 +168,7 @@ interface IConfig {
37168
trafficAllocation?: number,
38169
trafficAllocationSeed?: number,
39170
conditions?: Array<{
171+
type?: 'ROLLOUT' | 'WHITELIST';
40172
label: string;
41173
partitions: Array<IConfigPartition>;
42174
matchers: Array<IConfigMatcher>;
@@ -77,7 +209,6 @@ export function configsFetcherFactory(params: ISdkFactoryContextSync): IDefiniti
77209

78210
configsFetcher.type = 'configs' as const;
79211
return configsFetcher;
80-
81212
}
82213

83214
function defaultCondition(treatment: string): IDefinitionCondition {
@@ -96,25 +227,33 @@ function defaultCondition(treatment: string): IDefinitionCondition {
96227
};
97228
}
98229

230+
const wl = (d: { strings: string[] }) => ({ whitelistMatcherData: { whitelist: d.strings } });
231+
const num = (d: { type?: IConfigMatcherDataType; number: number }) => ({ unaryNumericMatcherData: { dataType: d.type || 'NUMBER', value: d.number } });
232+
const seg = (d: { name: string }) => ({ userDefinedSegmentMatcherData: { segmentName: d.name } });
233+
const str = (d: { string: string }) => ({ stringMatcherData: d.string });
234+
235+
const MATCHER_CONVERTERS: Record<IConfigMatcher['type'], (data: any) => Record<string, any>> = {
236+
ALL_KEYS: () => ({}),
237+
IN_SEGMENT: seg, IN_RULE_BASED_SEGMENT: seg,
238+
IN_LARGE_SEGMENT: (d) => ({ userDefinedLargeSegmentMatcherData: { largeSegmentName: d.name } }),
239+
WHITELIST: wl, EQUAL_TO_SET: wl, CONTAINS_ANY_OF_SET: wl, CONTAINS_ALL_OF_SET: wl,
240+
PART_OF_SET: wl, STARTS_WITH: wl, ENDS_WITH: wl, CONTAINS_STRING: wl, IN_LIST_SEMVER: wl,
241+
EQUAL_TO: num, GREATER_THAN_OR_EQUAL_TO: num, LESS_THAN_OR_EQUAL_TO: num,
242+
BETWEEN: (d) => ({ betweenMatcherData: { dataType: d.type || 'NUMBER', start: d.start, end: d.end } }),
243+
IN_CONFIG_VARIANT: (d) => ({ dependencyMatcherData: { split: d.config, treatments: d.variants } }),
244+
EQUAL_TO_BOOLEAN: (d) => ({ booleanMatcherData: d.value }),
245+
MATCHES_STRING: str, EQUAL_TO_SEMVER: str, GREATER_THAN_OR_EQUAL_TO_SEMVER: str, LESS_THAN_OR_EQUAL_TO_SEMVER: str,
246+
BETWEEN_SEMVER: (d) => ({ betweenStringMatcherData: { start: d.start, end: d.end } }),
247+
};
248+
99249
function convertMatcher(matcher: IConfigMatcher): IDefinitionMatcher {
100250
const keySelector = matcher.attribute ? { trafficType: 'user', attribute: matcher.attribute } : null;
101-
102-
switch (matcher.type) {
103-
case 'IS_EQUAL_TO':
104-
return {
105-
matcherType: 'EQUAL_TO',
106-
negate: false,
107-
keySelector,
108-
unaryNumericMatcherData: { dataType: matcher.data.type, value: matcher.data.number },
109-
};
110-
case 'WHITELIST':
111-
return {
112-
matcherType: 'WHITELIST',
113-
negate: false,
114-
keySelector,
115-
whitelistMatcherData: { whitelist: matcher.data.strings },
116-
};
117-
}
251+
return {
252+
matcherType: matcher.type,
253+
negate: false,
254+
keySelector,
255+
...MATCHER_CONVERTERS[matcher.type](matcher.data),
256+
} as IDefinitionMatcher;
118257
}
119258

120259
function convertConfigToDefinition(config: IConfig): IDefinition {
@@ -124,7 +263,7 @@ function convertConfigToDefinition(config: IConfig): IDefinition {
124263
config.variants.forEach(variant => configurations[variant.name] = variant.definition);
125264

126265
const conditions: IDefinitionCondition[] = config.targeting?.conditions?.map(condition => ({
127-
conditionType: condition.matchers.some((m: IConfigMatcher) => m.type === 'WHITELIST') ? 'WHITELIST' : 'ROLLOUT',
266+
conditionType: condition.type || (condition.matchers.some((m: IConfigMatcher) => m.type === 'WHITELIST') ? 'WHITELIST' : 'ROLLOUT'),
128267
label: condition.label,
129268
matcherGroup: {
130269
combiner: 'AND',
@@ -133,10 +272,13 @@ function convertConfigToDefinition(config: IConfig): IDefinition {
133272
partitions: condition.partitions.map(partition => ({ treatment: partition.variant, size: partition.size })),
134273
})) || [];
135274

136-
conditions.push(defaultCondition(defaultTreatment));
275+
// only add default condition if there is no a last condition with matcher type ALL_KEYS
276+
if (!conditions.some(condition => condition.matcherGroup.matchers.some(matcher => matcher.matcherType === 'ALL_KEYS'))) {
277+
conditions.push(defaultCondition(defaultTreatment));
278+
}
137279

138280
return {
139-
name: config.name,
281+
name: config.identifier,
140282
changeNumber: config.changeNumber || 0,
141283
status: 'ACTIVE',
142284
conditions,

types/splitio.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2360,9 +2360,9 @@ declare namespace SplitIO {
23602360
type JsonObject = { [key: string]: JsonValue; };
23612361

23622362
/**
2363-
* Config definition.
2363+
* Config object returned by getConfig.
23642364
*/
2365-
interface Config {
2365+
type Config = {
23662366
/**
23672367
* The name of the variant.
23682368
*/

0 commit comments

Comments
 (0)