diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/reducer.test.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/reducer.test.ts index 280551e9dbf06..5a71c9b34569f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/reducer.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/reducer.test.ts @@ -136,4 +136,47 @@ describe('Processors reducer', () => { }, ]); }); + + it('prevents moving a parent into child list', () => { + const processor1 = { type: 'test1', options: {} }; + const processor2 = { type: 'test2', options: {} }; + const processor3 = { type: 'test3', options: {} }; + const processor4 = { type: 'test4', options: {} }; + + const s1 = reducer(initialState, { + type: 'addTopLevelProcessor', + payload: { processor: processor1 }, + }); + + const s2 = reducer(s1, { + type: 'addTopLevelProcessor', + payload: { processor: processor2 }, + }); + + const s3 = reducer(s2, { + type: 'addOnFailureProcessor', + payload: { onFailureProcessor: processor3, targetSelector: ['1'] }, + }); + + const s4 = reducer(s3, { + type: 'addOnFailureProcessor', + payload: { onFailureProcessor: processor4, targetSelector: ['1', 'onFailure', '0'] }, + }); + + expect(s4.processors).toEqual([ + processor1, + { ...processor2, onFailure: [{ ...processor3, onFailure: [processor4] }] }, + ]); + + // Move the parent into a child list + const s5 = reducer(s4, { + type: 'moveProcessor', + payload: { + source: { selector: [], index: 1 }, + destination: { selector: ['1', 'onFailure', '0', 'onFailure', '0', 'onFailure'], index: 0 }, + }, + }); + + expect(s5.processors).toEqual(s4.processors); + }); }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/reducer.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/reducer.ts index edf1260feb0dd..b4f5fc5629d26 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/reducer.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/reducer.ts @@ -9,7 +9,7 @@ import { euiDragDropReorder } from '@elastic/eui'; import { OnFormUpdateArg } from '../../../shared_imports'; import { DeserializeResult } from './data_in'; -import { getValue, setValue, unsafeProcessorMove } from './utils'; +import { getValue, setValue, unsafeProcessorMove, PARENT_CHILD_NEST_ERROR } from './utils'; import { ProcessorInternal, DraggableLocation, ProcessorSelector } from './types'; type StateArg = DeserializeResult; @@ -62,11 +62,17 @@ export const reducer: Reducer = (state, action) => { euiDragDropReorder(getValue(path, state), source.index, destination.index) ); } else { - return setValue( - ['processors'], - state, - unsafeProcessorMove(state.processors, source, destination) - ); + try { + return setValue( + ['processors'], + state, + unsafeProcessorMove(state.processors, source, destination) + ); + } catch (e) { + if (e.message === PARENT_CHILD_NEST_ERROR) { + return { ...state }; + } + } } } diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts index 3416629bab1fd..7403d589ebf62 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/utils.ts @@ -87,6 +87,8 @@ export const setValue = ( return result!; }; +export const PARENT_CHILD_NEST_ERROR = 'PARENT_CHILD_NEST_ERROR'; + /** * Unsafe! * @@ -105,6 +107,10 @@ export const unsafeProcessorMove = ( source: DraggableLocation, destination: DraggableLocation ) => { + const selectorToSource = source.selector.concat(String(source.index)); + if (selectorToSource.every((pathSegment, idx) => pathSegment === destination.selector[idx])) { + throw new Error(PARENT_CHILD_NEST_ERROR); + } // Start by setting up references to objects of interest using our selectors // At this point, our selectors are consistent with the data passed in. const sourceProcessors = getValue(source.selector, processors);