Skip to content

Commit 284d1ee

Browse files
feat: add support for fallback treatments when client is not operational
1 parent 2251ad5 commit 284d1ee

File tree

8 files changed

+63
-44
lines changed

8 files changed

+63
-44
lines changed

.github/workflows/ci-cd.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
run: echo "VERSION=$(cat package.json | jq -r .version)" >> $GITHUB_ENV
5050

5151
- name: SonarQube Scan (Push)
52-
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/development')
52+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/fallback-treatment-support')
5353
uses: SonarSource/sonarcloud-github-action@v1.9
5454
env:
5555
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
@@ -77,7 +77,7 @@ jobs:
7777
-Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }}
7878
7979
- name: Store assets
80-
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/development')
80+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/fallback-treatment-support')
8181
uses: actions/upload-artifact@v4
8282
with:
8383
name: assets
@@ -88,7 +88,7 @@ jobs:
8888
name: Upload assets
8989
runs-on: ubuntu-latest
9090
needs: build
91-
if: github.event_name == 'push' && github.ref == 'refs/heads/development'
91+
if: github.event_name == 'push' && github.ref == 'refs/heads/fallback-treatment-support'
9292
strategy:
9393
matrix:
9494
environment:

package-lock.json

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

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio-react",
3-
"version": "2.5.0",
3+
"version": "2.5.1-rc.0",
44
"description": "A React library to easily integrate and use Split JS SDK",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",
@@ -63,7 +63,7 @@
6363
},
6464
"homepage": "https://github.com/splitio/react-client#readme",
6565
"dependencies": {
66-
"@splitsoftware/splitio": "11.7.2-rc.4",
66+
"@splitsoftware/splitio": "11.7.2-rc.5",
6767
"memoize-one": "^5.1.1",
6868
"shallowequal": "^1.1.0",
6969
"tslib": "^2.3.1"

src/useTreatment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ import { useSplitClient } from './useSplitClient';
1919
*/
2020
export function useTreatment(options: IUseTreatmentOptions): IUseTreatmentResult {
2121
const context = useSplitClient({ ...options, attributes: undefined });
22-
const { client, lastUpdate } = context;
22+
const { factory, client, lastUpdate } = context;
2323
const { name, attributes, properties } = options;
2424

2525
const getTreatment = React.useMemo(memoizeGetTreatment, []);
2626

2727
// Shallow copy `client.getAttributes` result for memoization, as it returns the same reference unless `client.clearAttributes` is invoked.
2828
// Note: the same issue occurs with the `names` and `attributes` arguments if they are mutated directly by the user instead of providing a new object.
29-
const treatment = getTreatment(client, lastUpdate, [name], attributes, client ? { ...client.getAttributes() } : {}, undefined, properties && { properties });
29+
const treatment = getTreatment(client, lastUpdate, [name], attributes, client ? { ...client.getAttributes() } : {}, undefined, properties && { properties }, factory);
3030

3131
return {
3232
...context,

src/useTreatmentWithConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ import { useSplitClient } from './useSplitClient';
1919
*/
2020
export function useTreatmentWithConfig(options: IUseTreatmentOptions): IUseTreatmentWithConfigResult {
2121
const context = useSplitClient({ ...options, attributes: undefined });
22-
const { client, lastUpdate } = context;
22+
const { factory, client, lastUpdate } = context;
2323
const { name, attributes, properties } = options;
2424

2525
const getTreatmentWithConfig = React.useMemo(memoizeGetTreatmentWithConfig, []);
2626

2727
// Shallow copy `client.getAttributes` result for memoization, as it returns the same reference unless `client.clearAttributes` is invoked.
2828
// Note: the same issue occurs with the `names` and `attributes` arguments if they are mutated directly by the user instead of providing a new object.
29-
const treatment = getTreatmentWithConfig(client, lastUpdate, [name], attributes, client ? { ...client.getAttributes() } : {}, undefined, properties && { properties });
29+
const treatment = getTreatmentWithConfig(client, lastUpdate, [name], attributes, client ? { ...client.getAttributes() } : {}, undefined, properties && { properties }, factory);
3030

3131
return {
3232
...context,

src/useTreatments.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ import { useSplitClient } from './useSplitClient';
2020
*/
2121
export function useTreatments(options: IUseTreatmentsOptions): IUseTreatmentsResult {
2222
const context = useSplitClient({ ...options, attributes: undefined });
23-
const { client, lastUpdate } = context;
23+
const { factory, client, lastUpdate } = context;
2424
const { names, flagSets, attributes, properties } = options;
2525

2626
const getTreatments = React.useMemo(memoizeGetTreatments, []);
2727

2828
// Shallow copy `client.getAttributes` result for memoization, as it returns the same reference unless `client.clearAttributes` is invoked.
2929
// Note: the same issue occurs with the `names` and `attributes` arguments if they are mutated directly by the user instead of providing a new object.
30-
const treatments = getTreatments(client, lastUpdate, names, attributes, client ? { ...client.getAttributes() } : {}, flagSets, properties && { properties });
30+
const treatments = getTreatments(client, lastUpdate, names, attributes, client ? { ...client.getAttributes() } : {}, flagSets, properties && { properties }, factory);
3131

3232
return {
3333
...context,

src/useTreatmentsWithConfig.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ import { useSplitClient } from './useSplitClient';
2020
*/
2121
export function useTreatmentsWithConfig(options: IUseTreatmentsOptions): IUseTreatmentsWithConfigResult {
2222
const context = useSplitClient({ ...options, attributes: undefined });
23-
const { client, lastUpdate } = context;
23+
const { factory, client, lastUpdate } = context;
2424
const { names, flagSets, attributes, properties } = options;
2525

2626
const getTreatmentsWithConfig = React.useMemo(memoizeGetTreatmentsWithConfig, []);
2727

2828
// Shallow copy `client.getAttributes` result for memoization, as it returns the same reference unless `client.clearAttributes` is invoked.
2929
// Note: the same issue occurs with the `names` and `attributes` arguments if they are mutated directly by the user instead of providing a new object.
30-
const treatments = getTreatmentsWithConfig(client, lastUpdate, names, attributes, client ? { ...client.getAttributes() } : {}, flagSets, properties && { properties });
30+
const treatments = getTreatmentsWithConfig(client, lastUpdate, names, attributes, client ? { ...client.getAttributes() } : {}, flagSets, properties && { properties }, factory);
3131

3232
return {
3333
...context,

src/utils.ts

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,24 @@ export function getStatus(client?: SplitIO.IBrowserClient): ISplitStatus {
3737
};
3838
}
3939

40+
function resolveFallback(flagName: string, withConfig: true, factory?: SplitIO.IBrowserSDK): SplitIO.TreatmentWithConfig;
41+
function resolveFallback(flagName: string, withConfig: false, factory?: SplitIO.IBrowserSDK): SplitIO.Treatment;
42+
function resolveFallback(flagName: string, withConfig: boolean, factory?: SplitIO.IBrowserSDK): SplitIO.Treatment | SplitIO.TreatmentWithConfig {
43+
if (factory && factory.settings.fallbackTreatments) {
44+
const fallbacks = factory.settings.fallbackTreatments;
45+
46+
const treatment = fallbacks.byFlag?.[flagName] || fallbacks.global;
47+
48+
if (treatment) {
49+
return isString(treatment) ?
50+
withConfig ? { treatment, config: null } : treatment :
51+
withConfig ? treatment : treatment.treatment;
52+
}
53+
}
54+
55+
return withConfig ? CONTROL_WITH_CONFIG : CONTROL;
56+
}
57+
4058
/**
4159
* Manage client attributes binding
4260
*/
@@ -45,9 +63,9 @@ export function initAttributes(client?: SplitIO.IBrowserClient, attributes?: Spl
4563
if (client && attributes) client.setAttributes(attributes);
4664
}
4765

48-
export function getControlTreatments(featureFlagNames: unknown, withConfig: true): SplitIO.TreatmentsWithConfig;
49-
export function getControlTreatments(featureFlagNames: unknown, withConfig: false): SplitIO.Treatments;
50-
export function getControlTreatments(featureFlagNames: unknown, withConfig: boolean): SplitIO.Treatments | SplitIO.TreatmentsWithConfig {
66+
export function getControlTreatments(featureFlagNames: unknown, withConfig: true, factory?: SplitIO.IBrowserSDK): SplitIO.TreatmentsWithConfig;
67+
export function getControlTreatments(featureFlagNames: unknown, withConfig: false, factory?: SplitIO.IBrowserSDK): SplitIO.Treatments;
68+
export function getControlTreatments(featureFlagNames: unknown, withConfig: boolean, factory?: SplitIO.IBrowserSDK): SplitIO.Treatments | SplitIO.TreatmentsWithConfig {
5169
// validate feature flag names
5270
if (!Array.isArray(featureFlagNames)) return {};
5371

@@ -56,9 +74,10 @@ export function getControlTreatments(featureFlagNames: unknown, withConfig: bool
5674
.map((featureFlagName) => featureFlagName.trim())
5775
.filter((featureFlagName) => featureFlagName.length > 0);
5876

59-
// return control treatments for each validated feature flag name
60-
return (featureFlagNames as string[]).reduce((pValue: SplitIO.Treatments | SplitIO.TreatmentsWithConfig, cValue: string) => {
61-
pValue[cValue] = withConfig ? CONTROL_WITH_CONFIG : CONTROL;
77+
// return control or fallback treatment for each validated feature flag name
78+
return (featureFlagNames as string[]).reduce((pValue: SplitIO.Treatments | SplitIO.TreatmentsWithConfig, featureFlagName: string) => {
79+
// @ts-expect-error asd
80+
pValue[featureFlagName] = resolveFallback(featureFlagName, withConfig, factory);
6281
return pValue;
6382
}, {});
6483
}
@@ -79,13 +98,13 @@ function argsAreEqual(newArgs: any[], lastArgs: any[]): boolean {
7998
shallowEqual(newArgs[5], lastArgs[5]); // flagSets
8099
}
81100

82-
function evaluateFeatureFlagsWithConfig(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names?: SplitIO.SplitNames, attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, flagSets?: string[], options?: SplitIO.EvaluationOptions) {
101+
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) {
83102
return client && client.getStatus().isOperational && (names || flagSets) ?
84103
names ?
85104
client.getTreatmentsWithConfig(names, attributes, options) :
86105
client.getTreatmentsWithConfigByFlagSets(flagSets!, attributes, options) :
87106
names ?
88-
getControlTreatments(names, true) :
107+
getControlTreatments(names, true, factory) :
89108
{} // empty object when evaluating with flag sets and client is not ready
90109
}
91110

@@ -97,34 +116,34 @@ export function memoizeGetTreatmentsWithConfig() {
97116
return memoizeOne(evaluateFeatureFlagsWithConfig, argsAreEqual);
98117
}
99118

100-
function evaluateFeatureFlags(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names?: SplitIO.SplitNames, attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, flagSets?: string[], options?: SplitIO.EvaluationOptions) {
119+
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) {
101120
return client && client.getStatus().isOperational && (names || flagSets) ?
102121
names ?
103122
client.getTreatments(names, attributes, options) :
104123
client.getTreatmentsByFlagSets(flagSets!, attributes, options) :
105124
names ?
106-
getControlTreatments(names, false) :
125+
getControlTreatments(names, false, factory) :
107126
{} // empty object when evaluating with flag sets and client is not ready
108127
}
109128

110129
export function memoizeGetTreatments() {
111130
return memoizeOne(evaluateFeatureFlags, argsAreEqual);
112131
}
113132

114-
function evaluateFeatureFlagWithConfig(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names: string[], attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, _flagSets?: undefined, options?: SplitIO.EvaluationOptions) {
133+
function evaluateFeatureFlagWithConfig(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names: string[], attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, _flagSets?: undefined, options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
115134
return client && client.getStatus().isOperational ?
116135
client.getTreatmentWithConfig(names[0], attributes, options) :
117-
CONTROL_WITH_CONFIG
136+
resolveFallback(names[0], true, factory);
118137
}
119138

120139
export function memoizeGetTreatmentWithConfig() {
121140
return memoizeOne(evaluateFeatureFlagWithConfig, argsAreEqual);
122141
}
123142

124-
function evaluateFeatureFlag(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names: string[], attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, _flagSets?: undefined, options?: SplitIO.EvaluationOptions) {
143+
function evaluateFeatureFlag(client: SplitIO.IBrowserClient | undefined, _lastUpdate: number, names: string[], attributes?: SplitIO.Attributes, _clientAttributes?: SplitIO.Attributes, _flagSets?: undefined, options?: SplitIO.EvaluationOptions, factory?: SplitIO.IBrowserSDK) {
125144
return client && client.getStatus().isOperational ?
126145
client.getTreatment(names[0], attributes, options) :
127-
CONTROL;
146+
resolveFallback(names[0], false, factory);
128147
}
129148

130149
export function memoizeGetTreatment() {

0 commit comments

Comments
 (0)