Skip to content

Commit

Permalink
Merge pull request #127 from splitio/polishing
Browse files Browse the repository at this point in the history
Add `MIGRATION-GUIDE.md` file
  • Loading branch information
EmilianoSanchez authored Nov 13, 2024
2 parents 9dbfcbc + 0c4c72f commit 080e7ae
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 47 deletions.
5 changes: 4 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
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. 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).
Expand Down
40 changes: 40 additions & 0 deletions MIGRATION-GUIDE.md
Original file line number Diff line number Diff line change
@@ -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' });
```
45 changes: 17 additions & 28 deletions src/__tests__/asyncActions.browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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<any>(initSplitSdk({ config: sdkBrowserConfig, onReadyFromCache: onReadyFromCacheCb }));
Expand All @@ -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);
Expand All @@ -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: {
Expand All @@ -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: {
Expand Down Expand Up @@ -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<any>(initSplitSdk({ config: sdkBrowserConfig, onTimedout: onTimedoutCb, onReadyFromCache: onReadyFromCacheCb, onReady: onReadyCb }));
Expand All @@ -401,15 +398,7 @@ describe('getTreatments', () => {
store.dispatch<any>(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
Expand All @@ -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: {
Expand All @@ -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: {
Expand All @@ -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: {
Expand All @@ -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: {
Expand All @@ -496,7 +485,7 @@ describe('getTreatments', () => {

// We deregister the item from evalOnUpdate.
store.dispatch<any>(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: {
Expand All @@ -508,22 +497,22 @@ 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: {
timestamp: expect.any(Number)
}
});

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);
Expand Down Expand Up @@ -676,7 +665,7 @@ describe('destroySplitSdk', () => {
const actionResult = store.dispatch<any>(destroySplitSdk());

actionResult.then(() => {
const action = store.getActions()[3];
const action = store.getActions()[1];
expect(action).toEqual({
type: SPLIT_DESTROY,
payload: {
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/reducer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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],
Expand Down
7 changes: 2 additions & 5 deletions src/asyncActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down Expand Up @@ -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
Expand Down
7 changes: 0 additions & 7 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
2 changes: 1 addition & 1 deletion src/react-redux/connectToggler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
*/
Expand Down

0 comments on commit 080e7ae

Please sign in to comment.