/** * External dependencies */ import { difference, isEqual, keyBy, map, omit, zip } from 'lodash'; import { isDefined } from '@nab/utils'; import type { AnyAction } from '@nab/types'; /** * Internal dependencies */ import { INIT_STATE as IS } from '../config'; import type { State as FullState } from '../types'; type State = FullState[ 'experiment' ][ 'alternatives' ]; import type { AlternativeAction } from '../actions/alternatives'; import type { SetupEditor } from '../actions/editor'; type Action = AlternativeAction | SetupEditor; const INIT_STATE = IS.experiment.alternatives; export function alternatives( state = INIT_STATE, action: AnyAction ): State { return actualReducer( state, action as Action ) ?? state; } function actualReducer( state: State, action: Action ): State { switch ( action.type ) { case 'ADD_ALTERNATIVES': { const newAlternatives = action.alternatives.filter( ( alt ) => ! isEqual( state.byId[ alt.id ], alt ) ); if ( ! newAlternatives.length ) { return state; } return { byId: { ...state.byId, ...keyBy( newAlternatives, 'id' ), }, allIds: [ ...state.allIds, ...map( newAlternatives, 'id' ) ], }; } case 'REMOVE_ALTERNATIVES': { const newAlternatives = { control: state.byId.control, ...omit( state.byId, action.ids ), }; const newAlternativeIds = difference( state.allIds, action.ids.filter( ( id ) => id !== 'control' ) ); if ( isEqual( state.byId, newAlternatives ) && isEqual( state.allIds, newAlternativeIds ) ) { return state; } return { byId: newAlternatives, allIds: newAlternativeIds, }; } case 'REPLACE_ALTERNATIVES': { const pairs = zip( action.oldIds, map( action.alternatives, 'id' ) ); const newAlternativesById = { control: state.byId.control, ...omit( state.byId, action.oldIds ), ...keyBy( action.alternatives, 'id' ), }; const newAlternativeIds = [ ...state.allIds .map( ( id ) => { const r = pairs.find( ( p ) => p[ 0 ] === id ); return r ? r[ 1 ] : id; } ) .filter( isDefined ), ...pairs .filter( ( p ) => ! p[ 0 ] ) .map( ( p ) => p[ 1 ] ) .filter( isDefined ), ]; const oldAlternatives = state.byId; const newAlternatives = newAlternativesById; const oldAlternativesWithoutId = Object.values( oldAlternatives ).map( ( r ) => omit( r, 'id' ) ); const newAlternativesWithoutId = Object.values( newAlternatives ).map( ( r ) => omit( r, 'id' ) ); if ( isEqual( oldAlternativesWithoutId, newAlternativesWithoutId ) ) { return state; } return { byId: newAlternativesById, allIds: newAlternativeIds, }; } case 'SET_ALTERNATIVE': const oldAlternative = state.byId[ action.id ]; if ( ! oldAlternative ) { return state; } const newAlternative = action.alternative; if ( ! newAlternative ) { return state; } if ( isEqual( oldAlternative, newAlternative ) ) { return state; } return { ...state, byId: { ...state.byId, [ action.id ]: newAlternative, }, }; case 'SETUP_EDITOR': { if ( 'nab/heatmap' === action.experiment.type ) { return INIT_STATE; } const newState = { byId: { control: state.byId.control, ...keyBy( action.experiment.alternatives, 'id' ), }, allIds: map( action.experiment.alternatives, 'id' ), }; return isEqual( state, newState ) ? state : newState; } } }