/** * External dependencies */ import { isEqual, keyBy, omit } 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' ][ 'segmentation' ]; import type { SegmentationAction } from '../actions/segmentation'; import type { SetupEditor } from '../actions/editor'; type Action = SegmentationAction | SetupEditor; const INIT_STATE = IS.experiment.segmentation; export function segmentation( 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_SEGMENTS': { const newSegments = action.segments.filter( ( segment ) => ! isEqual( state.segments[ segment.id ], segment ) ); if ( ! newSegments.length ) { return state; } return { ...state, segments: { ...state.segments, ...keyBy( newSegments, 'id' ), }, }; } case 'UPDATE_SEGMENT': { const oldSegment = state.segments[ action.segmentId ]; if ( ! oldSegment ) { return state; } const newSegment = { ...oldSegment, attributes: { ...oldSegment.attributes, ...action.attributes, }, }; if ( isEqual( newSegment, oldSegment ) ) { return state; } return { ...state, segments: { ...state.segments, [ action.segmentId ]: newSegment, }, }; } case 'REMOVE_SEGMENTS': { const newSegments = omit( state.segments, action.ids ); if ( isEqual( state.segments, newSegments ) ) { return state; } return { ...state, segments: newSegments, }; } case 'ADD_SEGMENTATION_RULES_INTO_SEGMENT': { const oldRules = state.rules[ action.segmentId ]; const newRules = action.segmentationRules.filter( ( rule ) => ! isEqual( oldRules?.[ rule.id ], rule ) ); if ( ! newRules.length ) { return state; } return { ...state, rules: { ...state.rules, [ action.segmentId ]: { ...oldRules, ...keyBy( newRules, 'id' ), }, }, }; } case 'REPLACE_SEGMENTATION_RULES_IN_SEGMENT': { const oldRules = state.rules[ action.segmentId ] ?? {}; const newRules = keyBy( action.segmentationRules, 'id' ); const oldRulesWithoutId = Object.values( oldRules ).map( ( r ) => omit( r, 'id' ) ); const newRulesWithoutId = Object.values( newRules ).map( ( r ) => omit( r, 'id' ) ); if ( isEqual( oldRulesWithoutId, newRulesWithoutId ) ) { return state; } return { ...state, rules: { ...state.rules, [ action.segmentId ]: newRules, }, }; } case 'UPDATE_SEGMENTATION_RULE': { const segment = state.rules[ action.segmentId ]; if ( ! segment ) { return state; } const oldSegmentationRule = segment[ action.segmentationRuleId ]; if ( ! oldSegmentationRule ) { return state; } const newSegmentationRule = { ...oldSegmentationRule, attributes: { ...oldSegmentationRule.attributes, ...action.attributes, }, }; if ( isEqual( oldSegmentationRule, newSegmentationRule ) ) { return state; } return { ...state, rules: { ...state.rules, [ action.segmentId ]: { ...segment, [ action.segmentationRuleId ]: newSegmentationRule, }, }, }; } case 'REMOVE_SEGMENTATION_RULES_FROM_SEGMENT': { const rules = state.rules[ action.segmentId ]; if ( ! rules ) { return state; } const newRules = omit( rules, action.segmentationRuleIds ); if ( isEqual( rules, newRules ) ) { return state; } return { ...state, rules: { ...state.rules, [ action.segmentId ]: newRules, }, }; } case 'SORT_SEGMENTS': return { ...state, segments: keyBy( action.ids .map( ( id ) => state.segments[ id ] ) .filter( isDefined ), 'id' ), }; case 'SET_SEGMENT_EVALUATION': return { ...state, evaluation: action.strategy, }; case 'SETUP_EDITOR': { if ( 'nab/heatmap' === action.experiment.type ) { return state; } const newState = { evaluation: action.experiment.segmentEvaluation, rules: action.experiment.segments.reduce( ( memo, segment ) => { memo[ segment.id ] = keyBy( segment.segmentationRules, 'id' ); return memo; }, {} as State[ 'rules' ] ), segments: keyBy( action.experiment.segments.map( ( segment ) => omit( segment, 'segmentationRules' ) ), 'id' ), }; return isEqual( state, newState ) ? state : newState; } } }