/** * External dependencies */ import { isEqual, keyBy, omit } from 'lodash'; import { EMPTY_OBJECT } 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' ][ 'conversionActions' ]; import type { CAAction } from '../actions/conversion-actions'; import type { SetupEditor } from '../actions/editor'; type Action = CAAction | SetupEditor; const INIT_STATE = IS.experiment.conversionActions; export function conversionActions( 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_CONVERSION_ACTIONS_INTO_GOAL': { const newConversionActions = action.conversionActions.filter( ( conversionAction ) => ! isEqual( state[ action.goalId ]?.[ conversionAction.id ], conversionAction ) ); if ( ! newConversionActions.length ) { return state; } return { ...state, [ action.goalId ]: { ...state[ action.goalId ], ...keyBy( newConversionActions, 'id' ), }, }; } case 'REPLACE_CONVERSION_ACTIONS_IN_GOAL': { const oldConvActions = state[ action.goalId ] ?? {}; const newConvActions = keyBy( action.conversionActions, 'id' ); const oldConvActionsWithoutId = Object.values( oldConvActions ).map( ( r ) => omit( r, 'id' ) ); const newConvActionsWithoutId = Object.values( newConvActions ).map( ( r ) => omit( r, 'id' ) ); if ( isEqual( oldConvActionsWithoutId, newConvActionsWithoutId ) ) { return state; } return { ...state, [ action.goalId ]: newConvActions, }; } case 'UPDATE_CONVERSION_ACTION': { const goal = state[ action.goalId ]; if ( ! goal ) { return state; } const oldConversionAction = goal[ action.conversionActionId ]; if ( ! oldConversionAction ) { return state; } const newConversionAction = { ...oldConversionAction, attributes: action.attributes ? { ...oldConversionAction.attributes, ...action.attributes, } : oldConversionAction.attributes, scope: action.scope ? action.scope : oldConversionAction.scope, }; if ( isEqual( oldConversionAction, newConversionAction ) ) { return state; } return { ...state, [ action.goalId ]: { ...goal, [ action.conversionActionId ]: newConversionAction, }, }; } case 'REMOVE_CONVERSION_ACTIONS_FROM_GOAL': { const newConversionActions = omit( state[ action.goalId ], action.conversionActionIds ); if ( isEqual( state[ action.goalId ], newConversionActions ) ) { return state; } return { ...state, [ action.goalId ]: newConversionActions, }; } case 'SETUP_EDITOR': { if ( 'nab/heatmap' === action.experiment.type ) { return EMPTY_OBJECT; } const newState = action.experiment.goals.reduce( ( memo, goal ) => { memo[ goal.id ] = keyBy( goal.conversionActions, 'id' ); return memo; }, {} as State ); return isEqual( state, newState ) ? state : newState; } } }