From e39e0d6c1d97eea3e0c8ce118bbb907488ed2206 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 12 Nov 2024 16:56:27 -0300 Subject: [PATCH 1/3] Fix typos --- src/__tests__/reducer.test.ts | 4 ++-- src/react-redux/connectToggler.ts | 2 +- src/types.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/__tests__/reducer.test.ts b/src/__tests__/reducer.test.ts index 10ef362..6f44306 100644 --- a/src/__tests__/reducer.test.ts +++ b/src/__tests__/reducer.test.ts @@ -2,7 +2,7 @@ import { initialStatus, splitReducer } from '../reducer'; import { splitReady, splitReadyWithEvaluations, splitReadyFromCache, splitReadyFromCacheWithEvaluations, splitTimedout, splitUpdate, splitUpdateWithEvaluations, splitDestroy, addTreatments } from '../actions'; import { ISplitState } from '../types'; import SplitIO from '@splitsoftware/splitio/types/splitio'; -import { AnyAction } from 'redux'; +import { Action } from 'redux'; const initialState: ISplitState = { isReady: false, @@ -179,7 +179,7 @@ describe('Split reducer', () => { }); }); - const actionCreatorsWithEvaluations: Array<[string, (key: SplitIO.SplitKey, treatments: SplitIO.TreatmentsWithConfig, timestamp: number, nonDefaultKey?: boolean) => AnyAction, boolean, boolean]> = [ + const actionCreatorsWithEvaluations: Array<[string, (key: SplitIO.SplitKey, treatments: SplitIO.TreatmentsWithConfig, timestamp: number, nonDefaultKey?: boolean) => Action, boolean, boolean]> = [ ['ADD_TREATMENTS', addTreatments, false, false], ['SPLIT_READY_WITH_EVALUATIONS', splitReadyWithEvaluations, true, false], ['SPLIT_READY_FROM_CACHE_WITH_EVALUATIONS', splitReadyFromCacheWithEvaluations, false, true], diff --git a/src/react-redux/connectToggler.ts b/src/react-redux/connectToggler.ts index c5421f3..3c072fa 100644 --- a/src/react-redux/connectToggler.ts +++ b/src/react-redux/connectToggler.ts @@ -8,7 +8,7 @@ const NullRenderComponent: React.ComponentType = () => null; /** * To avoid passing down dispatch property, merge props override default - * behaviour from connect. Here dispatchProps are not passing down. + * behavior from connect. Here dispatchProps are not passing down. */ const mergeProps = (stateProps: any, dispatchProps: any, ownProps: any) => ({ ...stateProps, diff --git a/src/types.ts b/src/types.ts index d4db8d2..cf287c6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -122,9 +122,9 @@ export type IGetTreatmentsParams = { /** * This param indicates to re-evaluate the feature flags if the SDK is updated. For example, a `true` value might be - * the desired behaviour for permission toggles or operation toggles, such as a kill switch, that you want to - * inmediately reflect in your app. A `false` value might be useful for experiment or release toggles, where - * you want to keep the treatment unchanged during the sesion of the user. + * the desired behavior for permission toggles or operation toggles, such as a kill switch, that you want to + * immediately reflect in your app. A `false` value might be useful for experiment or release toggles, where + * you want to keep the treatment unchanged during the session of the user. * * @defaultValue `false` */ From 947925dd10b25a233151662774d19deb5fa887e9 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 12 Nov 2024 18:55:36 -0300 Subject: [PATCH 2/3] Added MIGRATION-GUIDE.md file --- CHANGES.txt | 4 +++- MIGRATION-GUIDE.md | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 MIGRATION-GUIDE.md diff --git a/CHANGES.txt b/CHANGES.txt index dcd9169..d93f835 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -3,7 +3,9 @@ - Updated @splitsoftware/splitio package to version 11.0.1 that includes major updates, and updated some transitive dependencies for vulnerability fixes. - Renamed distribution folders from `/lib` to `/cjs` for CommonJS build, and `/es` to `/esm` for ECMAScript Modules build. - BREAKING CHANGES: - - Removed the `core.trafficType` option from the `config` object accepted by the `initSplitSdk` action creator, and made the `trafficType` argument of the `track` helper function mandatory. This is because traffic types can no longer be bound to SDK clients since JavaScript SDK v11.0.0, so the traffic type must now be provided as an argument in `track` method calls. + - Removed the `core.trafficType` option from the `config` object accepted by the `initSplitSdk` action creator, and made the `trafficType` argument of the `track` helper function mandatory. + This is because traffic types can no longer be bound to SDK clients since JavaScript SDK v11.0.0, so the traffic type must now be provided as an argument in `track` function calls. + Refer to ./MIGRATION-GUIDE.md for more details. 1.14.1 (October 15, 2024) - Bugfixing - Fixed error in `splitReducer` when handling actions with a `null` payload, preventing crashes caused by accessing undefined payload properties (Related to https://github.com/splitio/redux-client/issues/121). diff --git a/MIGRATION-GUIDE.md b/MIGRATION-GUIDE.md new file mode 100644 index 0000000..944b7fc --- /dev/null +++ b/MIGRATION-GUIDE.md @@ -0,0 +1,40 @@ + +# Migrating to Redux SDK v2.0.0 + +Redux SDK v2.0.0 introduces a breaking change that you should consider when migrating from a previous version. + +If you were passing the `core.trafficType` option to the SDK configuration object, you should remove it since it is no longer supported. +The `trafficType` must be passed as an argument of the `track` helper function. For example: + +```js +import { initSplitSdk, track } from '@splitsoftware/splitio-redux' + +const CONFIG = { + core: { + authorizationKey: YOUR_CLIENT_SIDE_SDK_KEY, + key: USER_KEY, + trafficType: 'user' + } +} + +store.dispatch(initSplitSdk({ config: CONFIG })) + +track({ eventType: 'my_event' }); +``` + +should be refactored to: + +```js +import { initSplitSdk, track } from '@splitsoftware/splitio-redux' + +const CONFIG = { + core: { + authorizationKey: YOUR_CLIENT_SIDE_SDK_KEY, + key: USER_KEY + } +} + +store.dispatch(initSplitSdk({ config: CONFIG })) + +track({ eventType: 'my_event', trafficType: 'user' }); +``` From 5e2967f2815d76614597776d75501155df5af232 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 12 Nov 2024 18:57:58 -0300 Subject: [PATCH 3/3] Updated 'getTreatments' action creator to not dispatch an action when called while the SDK is not operational --- CHANGES.txt | 1 + src/__tests__/asyncActions.browser.test.ts | 45 ++++++++-------------- src/asyncActions.ts | 7 +--- src/constants.ts | 7 ---- 4 files changed, 20 insertions(+), 40 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d93f835..cfee9bd 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,7 @@ 2.0.0 (November 15, 2024) - Added support for targeting rules based on large segments. - Updated @splitsoftware/splitio package to version 11.0.1 that includes major updates, and updated some transitive dependencies for vulnerability fixes. + - Updated `getTreatments` action creator to not dispatch an action when called while the SDK is not ready or ready from cache, to avoid unnecessary updates in the state. - Renamed distribution folders from `/lib` to `/cjs` for CommonJS build, and `/es` to `/esm` for ECMAScript Modules build. - BREAKING CHANGES: - Removed the `core.trafficType` option from the `config` object accepted by the `initSplitSdk` action creator, and made the `trafficType` argument of the `track` helper function mandatory. diff --git a/src/__tests__/asyncActions.browser.test.ts b/src/__tests__/asyncActions.browser.test.ts index 4d23117..6f004a3 100644 --- a/src/__tests__/asyncActions.browser.test.ts +++ b/src/__tests__/asyncActions.browser.test.ts @@ -13,7 +13,7 @@ import { sdkBrowserConfig } from './utils/sdkConfigs'; import { SPLIT_READY, SPLIT_READY_WITH_EVALUATIONS, SPLIT_READY_FROM_CACHE, SPLIT_READY_FROM_CACHE_WITH_EVALUATIONS, SPLIT_UPDATE, SPLIT_UPDATE_WITH_EVALUATIONS, SPLIT_TIMEDOUT, SPLIT_DESTROY, ADD_TREATMENTS, - ERROR_GETT_NO_INITSPLITSDK, ERROR_DESTROY_NO_INITSPLITSDK, getControlTreatmentsWithConfig, ERROR_GETT_NO_PARAM_OBJECT, + ERROR_GETT_NO_INITSPLITSDK, ERROR_DESTROY_NO_INITSPLITSDK, ERROR_GETT_NO_PARAM_OBJECT, } from '../constants'; /** Test targets */ @@ -323,7 +323,7 @@ describe('getTreatments', () => { } }); - it('stores control treatments (without calling SDK client) and registers pending evaluations if Split SDK is not operational, to dispatch it when ready (Using action result promise)', (done) => { + it('registers evaluations if Split SDK is not operational, to dispatch it when ready (Using action result promise)', (done) => { const store = mockStore(STATE_INITIAL); const actionResult = store.dispatch(initSplitSdk({ config: sdkBrowserConfig, onReadyFromCache: onReadyFromCacheCb })); @@ -332,10 +332,7 @@ describe('getTreatments', () => { // If SDK is not operational, ADD_TREATMENTS actions are dispatched, with control treatments for provided feature flag names, and no treatments for provided flag sets. - expect(store.getActions()).toEqual([ - { type: ADD_TREATMENTS, payload: { key: sdkBrowserConfig.core.key, treatments: getControlTreatmentsWithConfig(['split2']) } }, - { type: ADD_TREATMENTS, payload: { key: sdkBrowserConfig.core.key, treatments: {} } }, - ]); + expect(store.getActions().length).toBe(0); // SDK client is not called, but items are added to 'evalOnReady' list. expect(splitSdk.factory.client().getTreatmentsWithConfig).toBeCalledTimes(0); expect(splitSdk.factory.client().getTreatmentsWithConfigByFlagSets).toBeCalledTimes(0); @@ -345,8 +342,8 @@ describe('getTreatments', () => { // When the SDK is ready from cache, the SPLIT_READY_FROM_CACHE_WITH_EVALUATIONS action is not dispatched if the `getTreatments` action was dispatched with `evalOnReadyFromCache` false (splitSdk.factory as any).client().__emitter__.emit(Event.SDK_READY_FROM_CACHE); function onReadyFromCacheCb() { - expect(store.getActions().length).toBe(3); - const action = store.getActions()[2]; + expect(store.getActions().length).toBe(1); + const action = store.getActions()[0]; expect(action).toEqual({ type: SPLIT_READY_FROM_CACHE, payload: { @@ -359,7 +356,7 @@ describe('getTreatments', () => { actionResult.then(() => { // The SPLIT_READY_WITH_EVALUATIONS action is dispatched if the SDK is ready and there are pending evaluations. - const action = store.getActions()[3]; + const action = store.getActions()[1]; expect(action).toEqual({ type: SPLIT_READY_WITH_EVALUATIONS, payload: { @@ -392,7 +389,7 @@ describe('getTreatments', () => { }); }); - it('stores control treatments (without calling SDK client) and registers pending evaluations if Split SDK is not operational, to dispatch it when ready from cache, ready, and updated (Using callbacks to assert that registered evaluations are not affected when SDK timeout)', (done) => { + it('registers pending evaluations if Split SDK is not operational, to dispatch it when ready from cache, ready, and updated (Using callbacks to assert that registered evaluations are not affected when SDK timeout)', (done) => { const store = mockStore(STATE_INITIAL); store.dispatch(initSplitSdk({ config: sdkBrowserConfig, onTimedout: onTimedoutCb, onReadyFromCache: onReadyFromCacheCb, onReady: onReadyCb })); @@ -401,15 +398,7 @@ describe('getTreatments', () => { store.dispatch(getTreatments({ splitNames: 'split3', attributes, evalOnUpdate: true, evalOnReadyFromCache: true })); // If SDK is not ready, an ADD_TREATMENTS action is dispatched with control treatments without calling SDK client - expect(store.getActions().length).toBe(1); - let action = store.getActions()[0]; - expect(action).toEqual({ - type: ADD_TREATMENTS, - payload: { - key: sdkBrowserConfig.core.key, - treatments: getControlTreatmentsWithConfig(['split3']) - } - }); + expect(store.getActions().length).toBe(0); expect(splitSdk.factory.client().getTreatmentsWithConfig).toBeCalledTimes(0); // the item is added for evaluation on SDK_READY, and also on SDK_READY_FROM_CACHE and SDK_UPDATE events @@ -421,7 +410,7 @@ describe('getTreatments', () => { // When the SDK has timedout, the SPLIT_TIMEDOUT action is dispatched. It doesn't affect registered evaluations for other SDK events. (splitSdk.factory as any).client().__emitter__.emit(Event.SDK_READY_TIMED_OUT); function onTimedoutCb() { - action = store.getActions()[1]; + const action = store.getActions()[0]; expect(action).toEqual({ type: SPLIT_TIMEDOUT, payload: { @@ -434,7 +423,7 @@ describe('getTreatments', () => { // SPLIT_READY_FROM_CACHE, because of the `evalOnReadyFromCache` param in `getTreatments` action (splitSdk.factory as any).client().__emitter__.emit(Event.SDK_READY_FROM_CACHE); function onReadyFromCacheCb() { - action = store.getActions()[2]; + const action = store.getActions()[1]; expect(action).toEqual({ type: SPLIT_READY_FROM_CACHE_WITH_EVALUATIONS, payload: { @@ -456,7 +445,7 @@ describe('getTreatments', () => { // Using cb for ready event, because action result is rejected due to SDK timeout function onReadyCb() { // The SPLIT_READY_WITH_EVALUATIONS action is dispatched if the SDK is ready and there are pending evaluations. - action = store.getActions()[3]; + let action = store.getActions()[2]; expect(action).toEqual({ type: SPLIT_READY_WITH_EVALUATIONS, payload: { @@ -476,7 +465,7 @@ describe('getTreatments', () => { // Triggering an update dispatches SPLIT_UPDATE_WITH_EVALUATIONS (splitSdk.factory as any).client().__emitter__.emit(Event.SDK_UPDATE); - action = store.getActions()[4]; + action = store.getActions()[3]; expect(action).toEqual({ type: SPLIT_UPDATE_WITH_EVALUATIONS, payload: { @@ -496,7 +485,7 @@ describe('getTreatments', () => { // We deregister the item from evalOnUpdate. store.dispatch(getTreatments({ splitNames: 'split3', evalOnUpdate: false, key: { matchingKey: sdkBrowserConfig.core.key as string, bucketingKey: 'bucket' } })); - action = store.getActions()[5]; + action = store.getActions()[4]; expect(action).toEqual({ type: ADD_TREATMENTS, payload: { @@ -508,7 +497,7 @@ describe('getTreatments', () => { // Now, SDK_UPDATE events do not trigger SPLIT_UPDATE_WITH_EVALUATIONS but SPLIT_UPDATE instead (splitSdk.factory as any).client().__emitter__.emit(Event.SDK_UPDATE); - action = store.getActions()[6]; + action = store.getActions()[5]; expect(action).toEqual({ type: SPLIT_UPDATE, payload: { @@ -516,14 +505,14 @@ describe('getTreatments', () => { } }); - expect(store.getActions().length).toBe(7); // control assertion - no more actions after the update. + expect(store.getActions().length).toBe(6); // control assertion - no more actions after the update. expect(splitSdk.factory.client().getTreatmentsWithConfig).toBeCalledTimes(4); // control assertion - called 4 times, in actions SPLIT_READY_FROM_CACHE_WITH_EVALUATIONS, SPLIT_READY_WITH_EVALUATIONS, SPLIT_UPDATE_WITH_EVALUATIONS and ADD_TREATMENTS. done(); } }); - it('for non-default clients, it stores control treatments (without calling SDK client) and registers pending evaluations if the client is not operational, to dispatch it when ready from cache, ready, and updated (Using callbacks to assert that registered evaluations are not affected when the client timeouts)', (done) => { + it('for non-default clients, registers pending evaluations if the client is not operational, to dispatch it when ready from cache, ready, and updated (Using callbacks to assert that registered evaluations are not affected when the client timeouts)', (done) => { // Init SDK and set ready const store = mockStore(STATE_INITIAL); @@ -676,7 +665,7 @@ describe('destroySplitSdk', () => { const actionResult = store.dispatch(destroySplitSdk()); actionResult.then(() => { - const action = store.getActions()[3]; + const action = store.getActions()[1]; expect(action).toEqual({ type: SPLIT_DESTROY, payload: { diff --git a/src/asyncActions.ts b/src/asyncActions.ts index 84268c0..0bab016 100644 --- a/src/asyncActions.ts +++ b/src/asyncActions.ts @@ -3,7 +3,7 @@ import { SplitFactory as SplitFactoryForLocalhost } from '@splitsoftware/splitio import { Dispatch, Action } from 'redux'; import { IInitSplitSdkParams, IGetTreatmentsParams, IDestroySplitSdkParams, ISplitFactoryBuilder } from './types'; import { splitReady, splitReadyWithEvaluations, splitReadyFromCache, splitReadyFromCacheWithEvaluations, splitTimedout, splitUpdate, splitUpdateWithEvaluations, splitDestroy, addTreatments } from './actions'; -import { VERSION, ERROR_GETT_NO_INITSPLITSDK, ERROR_DESTROY_NO_INITSPLITSDK, getControlTreatmentsWithConfig } from './constants'; +import { VERSION, ERROR_GETT_NO_INITSPLITSDK, ERROR_DESTROY_NO_INITSPLITSDK } from './constants'; import { matching, __getStatus, validateGetTreatmentsParams, isMainClient } from './utils'; /** @@ -164,10 +164,7 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi splitReadyFromCacheWithEvaluations(params.key, treatments, status.lastUpdate, true) : addTreatments(params.key || (splitSdk.config as SplitIO.IBrowserSettings).core.key, treatments); } else { - // Otherwise, it adds control treatments to the store, without calling the SDK (no impressions sent) - // With flag sets, an empty object is passed since we don't know their feature flag names - // @TODO remove eventually to minimize state changes - return addTreatments(params.key || (splitSdk.config as SplitIO.IBrowserSettings).core.key, splitNames ? getControlTreatmentsWithConfig(splitNames) : {}); + return () => { }; } } else { // Split SDK running in Node.js diff --git a/src/constants.ts b/src/constants.ts index a2d00cb..f0e9eaa 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -15,13 +15,6 @@ export const CONTROL_WITH_CONFIG: SplitIO.TreatmentWithConfig = { config: null, }; -export const getControlTreatmentsWithConfig = (featureFlagNames: string[]): SplitIO.TreatmentsWithConfig => { - return featureFlagNames.reduce((pValue: SplitIO.TreatmentsWithConfig, cValue: string) => { - pValue[cValue] = CONTROL_WITH_CONFIG; - return pValue; - }, {}); -}; - // Action types export const SPLIT_READY = 'SPLIT_READY';