Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MIGRATION-GUIDE.md file #127

Merged
merged 4 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
EmilianoSanchez marked this conversation as resolved.
Show resolved Hide resolved
}
}

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
Loading