Skip to content

Commit 6fa03ad

Browse files
Merge pull request #254 from splitio/refactor-for-fallback-treatment
Refactor for code tree-shaking and fallback treatment
2 parents 93ac080 + 9144ded commit 6fa03ad

File tree

7 files changed

+119
-81
lines changed

7 files changed

+119
-81
lines changed

src/__tests__/utils.test.ts

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,61 @@
11
import { CONTROL, CONTROL_WITH_CONFIG } from '../constants';
2-
import { getControlTreatments } from '../utils';
2+
import { getTreatments, getTreatment } from '../utils';
3+
import { sdkBrowserWithConfig } from './testUtils/sdkConfigs';
34

4-
describe('getControlTreatments', () => {
5+
const factoryWithoutFallbacks = {
6+
settings: {}
7+
} as SplitIO.IBrowserSDK;
8+
9+
const factoryWithFallbacks = {
10+
settings: sdkBrowserWithConfig
11+
} as SplitIO.IBrowserSDK
12+
13+
describe('getTreatments', () => {
514

615
it('should return an empty object if an empty array is provided', () => {
7-
expect(Object.values(getControlTreatments([], true)).length).toBe(0);
8-
expect(Object.values(getControlTreatments([], false)).length).toBe(0);
16+
expect(getTreatments([], true)).toEqual({});
17+
expect(getTreatments([], false)).toEqual({});
918
});
1019

1120
it('should return an object with control treatments if an array of feature flag names is provided', () => {
1221
const featureFlagNames = ['split1', 'split2'];
13-
const treatmentsWithConfig: SplitIO.TreatmentsWithConfig = getControlTreatments(featureFlagNames, true);
14-
featureFlagNames.forEach((featureFlagName) => {
15-
expect(treatmentsWithConfig[featureFlagName]).toBe(CONTROL_WITH_CONFIG);
16-
});
17-
expect(Object.keys(treatmentsWithConfig).length).toBe(featureFlagNames.length);
18-
19-
const treatments: SplitIO.Treatments = getControlTreatments(featureFlagNames, false);
20-
featureFlagNames.forEach((featureFlagName) => {
21-
expect(treatments[featureFlagName]).toBe(CONTROL);
22-
});
23-
expect(Object.keys(treatments).length).toBe(featureFlagNames.length);
22+
const treatmentsWithConfig: SplitIO.TreatmentsWithConfig = getTreatments(featureFlagNames, true);
23+
expect(treatmentsWithConfig).toEqual({ 'split1': CONTROL_WITH_CONFIG, 'split2': CONTROL_WITH_CONFIG });
24+
25+
const treatments: SplitIO.Treatments = getTreatments(featureFlagNames, false);
26+
expect(treatments).toEqual({ 'split1': CONTROL, 'split2': CONTROL });
27+
28+
expect(getTreatments(featureFlagNames, true, factoryWithoutFallbacks)).toEqual({ 'split1': CONTROL_WITH_CONFIG, 'split2': CONTROL_WITH_CONFIG });
29+
expect(getTreatments(featureFlagNames, false, factoryWithoutFallbacks)).toEqual({ 'split1': CONTROL, 'split2': CONTROL });
30+
});
31+
32+
it('should return an object with fallback or control treatments if an array of feature flag names and factory are provided', () => {
33+
const featureFlagNames = ['split1', 'ff1'];
34+
const treatmentsWithConfig: SplitIO.TreatmentsWithConfig = getTreatments(featureFlagNames, true, factoryWithFallbacks);
35+
expect(treatmentsWithConfig).toEqual({ 'split1': { treatment: 'control_global', config: null }, 'ff1': { treatment: 'control_ff1', config: 'control_ff1_config' } });
36+
37+
const treatments: SplitIO.Treatments = getTreatments(featureFlagNames, false, factoryWithFallbacks);
38+
expect(treatments).toEqual({ 'split1': 'control_global', 'ff1': 'control_ff1' });
39+
});
40+
41+
});
42+
43+
describe('getTreatment', () => {
44+
45+
it('should return control treatments', () => {
46+
expect(getTreatment('any', true)).toEqual(CONTROL_WITH_CONFIG);
47+
expect(getTreatment('any', false)).toEqual(CONTROL);
48+
49+
expect(getTreatment('any', true, factoryWithoutFallbacks)).toEqual(CONTROL_WITH_CONFIG);
50+
expect(getTreatment('any', false, factoryWithoutFallbacks)).toEqual(CONTROL);
51+
});
52+
53+
it('should return fallback treatments if a factory with fallback treatments is provided', () => {
54+
const treatmentWithConfig: SplitIO.TreatmentWithConfig = getTreatment('split1', true, factoryWithFallbacks);
55+
expect(treatmentWithConfig).toEqual({ treatment: 'control_global', config: null });
56+
57+
const treatment: SplitIO.Treatment = getTreatment('ff1', false, factoryWithFallbacks);
58+
expect(treatment).toEqual('control_ff1' );
2459
});
2560

2661
});

src/__tests__/withSplitTreatments.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import { INITIAL_STATUS } from './testUtils/utils';
1414
import { withSplitFactory } from '../withSplitFactory';
1515
import { withSplitClient } from '../withSplitClient';
1616
import { withSplitTreatments } from '../withSplitTreatments';
17-
import { getControlTreatments } from '../utils';
17+
import { getTreatments } from '../utils';
1818

1919
const featureFlagNames = ['split1', 'split2'];
2020

2121
describe('withSplitTreatments', () => {
2222

2323
it(`passes Split props and outer props to the child.
24-
In this test, the value of "props.treatments" is obtained by the function "getControlTreatments",
24+
In this test, the value of "props.treatments" is obtained by the function "getTreatments",
2525
and not "client.getTreatmentsWithConfig" since the client is not ready.`, () => {
2626

2727
const Component = withSplitFactory(sdkBrowser)<{ outerProp1: string, outerProp2: number }>(
@@ -36,7 +36,7 @@ describe('withSplitTreatments', () => {
3636
...INITIAL_STATUS,
3737
factory: factory, client: clientMock,
3838
outerProp1: 'outerProp1', outerProp2: 2,
39-
treatments: getControlTreatments(featureFlagNames, true),
39+
treatments: getTreatments(featureFlagNames, true),
4040
});
4141

4242
return null;

src/useTreatment.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import * as React from 'react';
2-
import { memoizeGetTreatment } from './utils';
2+
import memoizeOne from 'memoize-one';
3+
import { argsAreEqual, getTreatment } from './utils';
34
import { IUseTreatmentResult, IUseTreatmentOptions } from './types';
45
import { useSplitClient } from './useSplitClient';
56

7+
function evaluateFeatureFlag(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names: string[], attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, _flagSets?: undefined, options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
8+
return client && client.getStatus().isOperational ?
9+
client.getTreatment(names[0], attributes, options) :
10+
getTreatment(names[0], false, factory);
11+
}
12+
13+
function memoizeGetTreatment() {
14+
return memoizeOne(evaluateFeatureFlag, argsAreEqual);
15+
}
16+
617
/**
718
* `useTreatment` is a hook that returns an Split Context object extended with a `treatment` property.
819
* It uses the `useSplitClient` hook to access the client, and invokes the `client.getTreatment()` method.

src/useTreatmentWithConfig.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import * as React from 'react';
2-
import { memoizeGetTreatmentWithConfig } from './utils';
2+
import memoizeOne from 'memoize-one';
3+
import { argsAreEqual, getTreatment } from './utils';
34
import { IUseTreatmentWithConfigResult, IUseTreatmentOptions } from './types';
45
import { useSplitClient } from './useSplitClient';
56

7+
function evaluateFeatureFlagWithConfig(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names: string[], attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, _flagSets?: undefined, options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
8+
return client && client.getStatus().isOperational ?
9+
client.getTreatmentWithConfig(names[0], attributes, options) :
10+
getTreatment(names[0], true, factory);
11+
}
12+
13+
function memoizeGetTreatmentWithConfig() {
14+
return memoizeOne(evaluateFeatureFlagWithConfig, argsAreEqual);
15+
}
16+
617
/**
718
* `useTreatmentWithConfig` is a hook that returns an Split Context object extended with a `treatment` property.
819
* It uses the `useSplitClient` hook to access the client, and invokes the `client.getTreatmentWithConfig()` method.

src/useTreatments.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
import * as React from 'react';
2-
import { memoizeGetTreatments } from './utils';
2+
import memoizeOne from 'memoize-one';
3+
import { argsAreEqual, getTreatments } from './utils';
34
import { IUseTreatmentsResult, IUseTreatmentsOptions } from './types';
45
import { useSplitClient } from './useSplitClient';
56

7+
function evaluateFeatureFlags(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names?: SplitIO.SplitNames, attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, flagSets?: string[], options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
8+
return client && client.getStatus().isOperational && (names || flagSets) ?
9+
names ?
10+
client.getTreatments(names, attributes, options) :
11+
client.getTreatmentsByFlagSets(flagSets!, attributes, options) :
12+
names ?
13+
getTreatments(names, false, factory) :
14+
{} // empty object when evaluating with flag sets and client is not ready
15+
}
16+
17+
export function memoizeGetTreatments() {
18+
return memoizeOne(evaluateFeatureFlags, argsAreEqual);
19+
}
20+
621
/**
722
* `useTreatments` is a hook that returns an Split Context object extended with a `treatments` property object that contains feature flag evaluations.
823
* It uses the `useSplitClient` hook to access the client, and invokes the `client.getTreatments()` method if the `names` option is provided,

src/useTreatmentsWithConfig.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
import * as React from 'react';
2-
import { memoizeGetTreatmentsWithConfig } from './utils';
2+
import memoizeOne from 'memoize-one';
3+
import { argsAreEqual, getTreatments } from './utils';
34
import { IUseTreatmentsOptions, IUseTreatmentsWithConfigResult } from './types';
45
import { useSplitClient } from './useSplitClient';
56

7+
function evaluateFeatureFlagsWithConfig(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names?: SplitIO.SplitNames, attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, flagSets?: string[], options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
8+
return client && client.getStatus().isOperational && (names || flagSets) ?
9+
names ?
10+
client.getTreatmentsWithConfig(names, attributes, options) :
11+
client.getTreatmentsWithConfigByFlagSets(flagSets!, attributes, options) :
12+
names ?
13+
getTreatments(names, true, factory) :
14+
{} // empty object when evaluating with flag sets and client is not ready
15+
}
16+
17+
function memoizeGetTreatmentsWithConfig() {
18+
return memoizeOne(evaluateFeatureFlagsWithConfig, argsAreEqual);
19+
}
20+
621
/**
722
* `useTreatmentsWithConfig` is a hook that returns an Split Context object extended with a `treatments` property object that contains feature flag evaluations.
823
* It uses the `useSplitClient` hook to access the client, and invokes the `client.getTreatmentsWithConfig()` method if the `names` option is provided,

src/utils.ts

Lines changed: 10 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import memoizeOne from 'memoize-one';
21
import shallowEqual from 'shallowequal';
32
import { CONTROL, CONTROL_WITH_CONFIG } from './constants';
43
import { ISplitStatus } from './types';
@@ -40,12 +39,12 @@ export function initAttributes(client?: SplitIO.IBrowserClient, attributes?: Spl
4039
if (client && attributes) client.setAttributes(attributes);
4140
}
4241

43-
// Utils used to retrieve treatments when the client is not operational:
42+
// Utils used to retrieve fallback or control treatments when the client is not operational:
4443

45-
function resolveFallback(flagName: string, withConfig: true, factory?: SplitIO.IBrowserSDK): SplitIO.TreatmentWithConfig;
46-
function resolveFallback(flagName: string, withConfig: false, factory?: SplitIO.IBrowserSDK): SplitIO.Treatment;
47-
function resolveFallback(flagName: string, withConfig: boolean, factory?: SplitIO.IBrowserSDK): SplitIO.Treatment | SplitIO.TreatmentWithConfig;
48-
function resolveFallback(flagName: string, withConfig: boolean, factory?: SplitIO.IBrowserSDK) {
44+
export function getTreatment(flagName: string, withConfig: true, factory?: SplitIO.IBrowserSDK): SplitIO.TreatmentWithConfig;
45+
export function getTreatment(flagName: string, withConfig: false, factory?: SplitIO.IBrowserSDK): SplitIO.Treatment;
46+
export function getTreatment(flagName: string, withConfig: boolean, factory?: SplitIO.IBrowserSDK): SplitIO.Treatment | SplitIO.TreatmentWithConfig;
47+
export function getTreatment(flagName: string, withConfig: boolean, factory?: SplitIO.IBrowserSDK) {
4948
if (factory && factory.settings.fallbackTreatments) {
5049
const fallbacks = factory.settings.fallbackTreatments;
5150

@@ -61,9 +60,9 @@ function resolveFallback(flagName: string, withConfig: boolean, factory?: SplitI
6160
return withConfig ? CONTROL_WITH_CONFIG : CONTROL;
6261
}
6362

64-
export function getControlTreatments(featureFlagNames: unknown, withConfig: true, factory?: SplitIO.IBrowserSDK): SplitIO.TreatmentsWithConfig;
65-
export function getControlTreatments(featureFlagNames: unknown, withConfig: false, factory?: SplitIO.IBrowserSDK): SplitIO.Treatments;
66-
export function getControlTreatments(featureFlagNames: unknown, withConfig: boolean, factory?: SplitIO.IBrowserSDK) {
63+
export function getTreatments(featureFlagNames: unknown, withConfig: true, factory?: SplitIO.IBrowserSDK): SplitIO.TreatmentsWithConfig;
64+
export function getTreatments(featureFlagNames: unknown, withConfig: false, factory?: SplitIO.IBrowserSDK): SplitIO.Treatments;
65+
export function getTreatments(featureFlagNames: unknown, withConfig: boolean, factory?: SplitIO.IBrowserSDK) {
6766
// validate feature flag names
6867
if (!Array.isArray(featureFlagNames)) return {};
6968

@@ -74,7 +73,7 @@ export function getControlTreatments(featureFlagNames: unknown, withConfig: bool
7473

7574
// return control or fallback treatment for each validated feature flag name
7675
return (featureFlagNames as string[]).reduce((pValue: SplitIO.Treatments | SplitIO.TreatmentsWithConfig, featureFlagName: string) => {
77-
pValue[featureFlagName] = resolveFallback(featureFlagName, withConfig, factory);
76+
pValue[featureFlagName] = getTreatment(featureFlagName, withConfig, factory);
7877
return pValue;
7978
}, {});
8079
}
@@ -84,59 +83,11 @@ export function getControlTreatments(featureFlagNames: unknown, withConfig: bool
8483
* The result treatments are the same given the same `client` instance, `lastUpdate` timestamp, and list of feature flag names and attributes.
8584
*/
8685

87-
function argsAreEqual(newArgs: any[], lastArgs: any[]): boolean {
86+
export function argsAreEqual(newArgs: any[], lastArgs: any[]): boolean {
8887
return newArgs[0] === lastArgs[0] && // client
8988
newArgs[1] === lastArgs[1] && // lastUpdate
9089
shallowEqual(newArgs[2], lastArgs[2]) && // names
9190
shallowEqual(newArgs[3], lastArgs[3]) && // attributes
9291
shallowEqual(newArgs[4], lastArgs[4]) && // client attributes
9392
shallowEqual(newArgs[5], lastArgs[5]); // flagSets
9493
}
95-
96-
function evaluateFeatureFlagsWithConfig(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names?: SplitIO.SplitNames, attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, flagSets?: string[], options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
97-
return client && client.getStatus().isOperational && (names || flagSets) ?
98-
names ?
99-
client.getTreatmentsWithConfig(names, attributes, options) :
100-
client.getTreatmentsWithConfigByFlagSets(flagSets!, attributes, options) :
101-
names ?
102-
getControlTreatments(names, true, factory) :
103-
{} // empty object when evaluating with flag sets and client is not ready
104-
}
105-
106-
export function memoizeGetTreatmentsWithConfig() {
107-
return memoizeOne(evaluateFeatureFlagsWithConfig, argsAreEqual);
108-
}
109-
110-
function evaluateFeatureFlags(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names?: SplitIO.SplitNames, attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, flagSets?: string[], options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
111-
return client && client.getStatus().isOperational && (names || flagSets) ?
112-
names ?
113-
client.getTreatments(names, attributes, options) :
114-
client.getTreatmentsByFlagSets(flagSets!, attributes, options) :
115-
names ?
116-
getControlTreatments(names, false, factory) :
117-
{} // empty object when evaluating with flag sets and client is not ready
118-
}
119-
120-
export function memoizeGetTreatments() {
121-
return memoizeOne(evaluateFeatureFlags, argsAreEqual);
122-
}
123-
124-
function evaluateFeatureFlagWithConfig(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names: string[], attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, _flagSets?: undefined, options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
125-
return client && client.getStatus().isOperational ?
126-
client.getTreatmentWithConfig(names[0], attributes, options) :
127-
resolveFallback(names[0], true, factory);
128-
}
129-
130-
export function memoizeGetTreatmentWithConfig() {
131-
return memoizeOne(evaluateFeatureFlagWithConfig, argsAreEqual);
132-
}
133-
134-
function evaluateFeatureFlag(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names: string[], attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, _flagSets?: undefined, options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
135-
return client && client.getStatus().isOperational ?
136-
client.getTreatment(names[0], attributes, options) :
137-
resolveFallback(names[0], false, factory);
138-
}
139-
140-
export function memoizeGetTreatment() {
141-
return memoizeOne(evaluateFeatureFlag, argsAreEqual);
142-
}

0 commit comments

Comments
 (0)