Skip to content

Commit 8d375a4

Browse files
convert to dynamic config; return boolean, numeric and string responses (FF-860) (#9)
* add support for dynamic config types (FF-860) * handle test case 5 and 6; add github test data repo * remove deleting .git directory; not sure why make test-data is failing on github actions * trying to get the makefile to work * aaron feedback: private constructor; toString function * v1.4.0
1 parent 58a96af commit 8d375a4

15 files changed

+424
-65
lines changed

Makefile

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,19 @@ help: Makefile
2525

2626
## test-data
2727
testDataDir := test/data/
28+
tempDir := ${testDataDir}temp/
29+
gitDataDir := ${tempDir}sdk-test-data/
30+
branchName := main
31+
githubRepoLink := https://github.com/Eppo-exp/sdk-test-data.git
32+
repoName := sdk-test-data
2833
.PHONY: test-data
29-
test-data:
34+
test-data:
3035
rm -rf $(testDataDir)
31-
mkdir -p $(testDataDir)
32-
gsutil cp gs://sdk-test-data/rac-experiments-v3.json $(testDataDir)
33-
gsutil cp -r gs://sdk-test-data/assignment-v2 $(testDataDir)
36+
mkdir -p $(tempDir)
37+
git clone -b ${branchName} --depth 1 --single-branch ${githubRepoLink} ${gitDataDir}
38+
cp ${gitDataDir}rac-experiments-v3.json ${testDataDir}
39+
cp -r ${gitDataDir}assignment-v2 ${testDataDir}
40+
rm -rf ${tempDir}
3441

3542
## prepare
3643
.PHONY: prepare

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@eppo/js-client-sdk-common",
3-
"version": "1.3.1",
3+
"version": "1.4.0",
44
"description": "Eppo SDK for client-side JavaScript applications (base for both web and react native)",
55
"main": "dist/index.js",
66
"files": [

src/assignment-hooks.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { EppoValue } from './eppo_value';
2+
13
/**
24
* Implement this interface to override an assignment or receive a callback post assignment
35
* @public
@@ -12,7 +14,7 @@ export interface IAssignmentHooks {
1214
* then the subject will be assigned with the default assignment logic.
1315
* @public
1416
*/
15-
onPreAssignment(experimentKey: string, subject: string): string | null;
17+
onPreAssignment(experimentKey: string, subject: string): EppoValue | null;
1618

1719
/**
1820
* Invoked after a subject is assigned. Useful for any post assignment logic needed which is specific
@@ -22,5 +24,5 @@ export interface IAssignmentHooks {
2224
* @param variation the assigned variation
2325
* @public
2426
*/
25-
onPostAssignment(experimentKey: string, subject: string, variation: string): void;
27+
onPostAssignment(experimentKey: string, subject: string, variation: EppoValue | null): void;
2628
}

src/client/eppo-client.spec.ts

Lines changed: 99 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import mock from 'xhr-mock';
77

88
import {
99
IAssignmentTestCase,
10+
ValueTestType,
1011
readAssignmentTestData,
1112
readMockRacResponse,
1213
} from '../../test/testHelpers';
@@ -15,6 +16,7 @@ import { IAssignmentLogger } from '../assignment-logger';
1516
import { IConfigurationStore } from '../configuration-store';
1617
import { MAX_EVENT_QUEUE_SIZE } from '../constants';
1718
import { OperatorType } from '../dto/rule-dto';
19+
import { EppoValue } from '../eppo_value';
1820
import ExperimentConfigurationRequestor from '../experiment-configuration-requestor';
1921
import HttpClient from '../http-client';
2022

@@ -85,6 +87,7 @@ describe('EppoClient E2E test', () => {
8587
enabled: true,
8688
subjectShards: 100,
8789
overrides: {},
90+
typedOverrides: {},
8891
rules: [
8992
{
9093
allocationKey: 'allocation1',
@@ -98,6 +101,7 @@ describe('EppoClient E2E test', () => {
98101
{
99102
name: 'control',
100103
value: 'control',
104+
typedValue: 'control',
101105
shardRange: {
102106
start: 0,
103107
end: 34,
@@ -106,6 +110,7 @@ describe('EppoClient E2E test', () => {
106110
{
107111
name: 'variant-1',
108112
value: 'variant-1',
113+
typedValue: 'variant-1',
109114
shardRange: {
110115
start: 34,
111116
end: 67,
@@ -114,6 +119,7 @@ describe('EppoClient E2E test', () => {
114119
{
115120
name: 'variant-2',
116121
value: 'variant-2',
122+
typedValue: 'variant-2',
117123
shardRange: {
118124
start: 67,
119125
end: 100,
@@ -172,17 +178,34 @@ describe('EppoClient E2E test', () => {
172178
'test variation assignment splits',
173179
async ({
174180
experiment,
181+
valueType = ValueTestType.StringType,
175182
subjects,
176183
subjectsWithAttributes,
177184
expectedAssignments,
178185
}: IAssignmentTestCase) => {
179186
`---- Test Case for ${experiment} Experiment ----`;
180-
const assignments = subjectsWithAttributes
181-
? getAssignmentsWithSubjectAttributes(subjectsWithAttributes, experiment)
182-
: getAssignments(subjects, experiment);
183187

184-
// temporarily cast to string under dynamic configuration support is added
185-
expect(assignments).toEqual(expectedAssignments.map((e) => (e ? e.toString() : null)));
188+
const assignments = subjectsWithAttributes
189+
? getAssignmentsWithSubjectAttributes(subjectsWithAttributes, experiment, valueType)
190+
: getAssignments(subjects, experiment, valueType);
191+
192+
switch (valueType) {
193+
case ValueTestType.BoolType: {
194+
const boolAssignments = assignments.map((a) => a?.boolValue ?? null);
195+
expect(boolAssignments).toEqual(expectedAssignments);
196+
break;
197+
}
198+
case ValueTestType.NumericType: {
199+
const numericAssignments = assignments.map((a) => a?.numericValue ?? null);
200+
expect(numericAssignments).toEqual(expectedAssignments);
201+
break;
202+
}
203+
case ValueTestType.StringType: {
204+
const stringAssignments = assignments.map((a) => a?.stringValue ?? null);
205+
expect(stringAssignments).toEqual(expectedAssignments);
206+
break;
207+
}
208+
}
186209
},
187210
);
188211
});
@@ -198,7 +221,7 @@ describe('EppoClient E2E test', () => {
198221
experimentName,
199222
JSON.stringify({
200223
...mockExperimentConfig,
201-
overrides: {
224+
typedOverrides: {
202225
'1b50f33aef8f681a13f623963da967ed': 'control',
203226
},
204227
}),
@@ -212,7 +235,7 @@ describe('EppoClient E2E test', () => {
212235
const entry = {
213236
...mockExperimentConfig,
214237
enabled: false,
215-
overrides: {
238+
typedOverrides: {
216239
'1b50f33aef8f681a13f623963da967ed': 'control',
217240
},
218241
};
@@ -281,9 +304,29 @@ describe('EppoClient E2E test', () => {
281304
expect(assignment).toEqual('control');
282305
});
283306

284-
function getAssignments(subjects: string[], experiment: string): string[] {
307+
function getAssignments(
308+
subjects: string[],
309+
experiment: string,
310+
valueTestType: ValueTestType = ValueTestType.StringType,
311+
): (EppoValue | null)[] {
285312
return subjects.map((subjectKey) => {
286-
return globalClient.getAssignment(subjectKey, experiment);
313+
switch (valueTestType) {
314+
case ValueTestType.BoolType: {
315+
const ba = globalClient.getBoolAssignment(subjectKey, experiment);
316+
if (ba === null) return null;
317+
return EppoValue.Bool(ba);
318+
}
319+
case ValueTestType.NumericType: {
320+
const na = globalClient.getNumericAssignment(subjectKey, experiment);
321+
if (na === null) return null;
322+
return EppoValue.Numeric(na);
323+
}
324+
case ValueTestType.StringType: {
325+
const sa = globalClient.getStringAssignment(subjectKey, experiment);
326+
if (sa === null) return null;
327+
return EppoValue.String(sa);
328+
}
329+
}
287330
});
288331
}
289332

@@ -294,9 +337,38 @@ describe('EppoClient E2E test', () => {
294337
subjectAttributes: Record<string, any>;
295338
}[],
296339
experiment: string,
297-
): string[] {
340+
valueTestType: ValueTestType = ValueTestType.StringType,
341+
): (EppoValue | null)[] {
298342
return subjectsWithAttributes.map((subject) => {
299-
return globalClient.getAssignment(subject.subjectKey, experiment, subject.subjectAttributes);
343+
switch (valueTestType) {
344+
case ValueTestType.BoolType: {
345+
const ba = globalClient.getBoolAssignment(
346+
subject.subjectKey,
347+
experiment,
348+
subject.subjectAttributes,
349+
);
350+
if (ba === null) return null;
351+
return EppoValue.Bool(ba);
352+
}
353+
case ValueTestType.NumericType: {
354+
const na = globalClient.getNumericAssignment(
355+
subject.subjectKey,
356+
experiment,
357+
subject.subjectAttributes,
358+
);
359+
if (na === null) return null;
360+
return EppoValue.Numeric(na);
361+
}
362+
case ValueTestType.StringType: {
363+
const sa = globalClient.getStringAssignment(
364+
subject.subjectKey,
365+
experiment,
366+
subject.subjectAttributes,
367+
);
368+
if (sa === null) return null;
369+
return EppoValue.String(sa);
370+
}
371+
}
300372
});
301373
}
302374

@@ -324,12 +396,16 @@ describe('EppoClient E2E test', () => {
324396
{},
325397
{
326398
// eslint-disable-next-line @typescript-eslint/no-unused-vars
327-
onPreAssignment(experimentKey: string, subject: string): string {
328-
return 'my-overridden-variation';
399+
onPreAssignment(experimentKey: string, subject: string): EppoValue | null {
400+
return EppoValue.String('my-overridden-variation');
329401
},
330402

331403
// eslint-disable-next-line @typescript-eslint/no-unused-vars
332-
onPostAssignment(experimentKey: string, subject: string, variation: string): void {
404+
onPostAssignment(
405+
experimentKey: string,
406+
subject: string,
407+
variation: EppoValue | null,
408+
): void {
333409
// no-op
334410
},
335411
},
@@ -345,12 +421,16 @@ describe('EppoClient E2E test', () => {
345421
{},
346422
{
347423
// eslint-disable-next-line @typescript-eslint/no-unused-vars
348-
onPreAssignment(experimentKey: string, subject: string): string | null {
424+
onPreAssignment(experimentKey: string, subject: string): EppoValue | null {
349425
return null;
350426
},
351427

352428
// eslint-disable-next-line @typescript-eslint/no-unused-vars
353-
onPostAssignment(experimentKey: string, subject: string, variation: string): void {
429+
onPostAssignment(
430+
experimentKey: string,
431+
subject: string,
432+
variation: EppoValue | null,
433+
): void {
354434
// no-op
355435
},
356436
},
@@ -369,7 +449,9 @@ describe('EppoClient E2E test', () => {
369449
expect(td.explain(mockHooks.onPostAssignment).callCount).toEqual(1);
370450
expect(td.explain(mockHooks.onPostAssignment).calls[0].args[0]).toEqual(experimentName);
371451
expect(td.explain(mockHooks.onPostAssignment).calls[0].args[1]).toEqual(subject);
372-
expect(td.explain(mockHooks.onPostAssignment).calls[0].args[2]).toEqual(variation);
452+
expect(td.explain(mockHooks.onPostAssignment).calls[0].args[2]).toEqual(
453+
EppoValue.String(variation ?? ''),
454+
);
373455
});
374456
});
375457
});

0 commit comments

Comments
 (0)