Skip to content

Commit

Permalink
Updated 'getTreatments' action creator to not dispatch an action when…
Browse files Browse the repository at this point in the history
… called while the SDK is not operational
  • Loading branch information
EmilianoSanchez committed Nov 12, 2024
1 parent 947925d commit 5e2967f
Show file tree
Hide file tree
Showing 4 changed files with 20 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
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
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

0 comments on commit 5e2967f

Please sign in to comment.