From 833d76442f70ae5a8c2497e1e0ef01033f03b2e2 Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 31 Aug 2020 09:34:39 -0400 Subject: [PATCH] [Ingest pipelines] Tests for pipeline debugging enhancement (#75606) --- .../__jest__/constants.ts | 75 ++++++ .../__jest__/http_requests.helpers.ts | 45 ++++ .../__jest__/test_pipeline.helpers.tsx | 231 +++++++++++++++++ .../__jest__/test_pipeline.test.tsx | 240 ++++++++++++++++++ .../documents_dropdown/documents_dropdown.tsx | 1 + .../processor_output.tsx | 4 +- .../pipeline_processors_editor_item.tsx | 1 + ...pipeline_processors_editor_item_status.tsx | 8 +- .../test_pipeline/test_output_button.tsx | 4 +- .../test_pipeline/test_pipeline_flyout.tsx | 1 + .../tab_documents.tsx | 7 +- .../test_pipeline_flyout_tabs/tab_output.tsx | 6 +- .../test_pipeline_tabs.tsx | 2 +- .../deserialize.test.ts | 168 ++++++++---- .../pipeline_processors_editor/types.ts | 2 +- 15 files changed, 735 insertions(+), 60 deletions(-) create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/constants.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/http_requests.helpers.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/constants.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/constants.ts new file mode 100644 index 0000000000000..e75ba56277c5c --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/constants.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Pipeline } from '../../../../../common/types'; +import { VerboseTestOutput, Document } from '../types'; + +export const PROCESSORS: Pick = { + processors: [ + { + set: { + field: 'field1', + value: 'value1', + }, + }, + ], +}; + +export const DOCUMENTS: Document[] = [ + { + _index: 'index', + _id: 'id1', + _source: { + name: 'foo', + }, + }, + { + _index: 'index', + _id: 'id2', + _source: { + name: 'bar', + }, + }, +]; + +export const SIMULATE_RESPONSE: VerboseTestOutput = { + docs: [ + { + processor_results: [ + { + processor_type: 'set', + status: 'success', + tag: 'some_tag', + doc: { + _index: 'index', + _id: 'id1', + _source: { + name: 'foo', + foo: 'bar', + }, + }, + }, + ], + }, + { + processor_results: [ + { + processor_type: 'set', + status: 'success', + tag: 'some_tag', + doc: { + _index: 'index', + _id: 'id2', + _source: { + name: 'bar', + foo: 'bar', + }, + }, + }, + ], + }, + ], +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/http_requests.helpers.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/http_requests.helpers.ts new file mode 100644 index 0000000000000..541a6853a99b3 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/http_requests.helpers.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import sinon, { SinonFakeServer } from 'sinon'; + +type HttpResponse = Record | any[]; + +// Register helpers to mock HTTP Requests +const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { + const setSimulatePipelineResponse = (response?: HttpResponse, error?: any) => { + const status = error ? error.status || 400 : 200; + const body = error ? JSON.stringify(error.body) : JSON.stringify(response); + + server.respondWith('POST', '/api/ingest_pipelines/simulate', [ + status, + { 'Content-Type': 'application/json' }, + body, + ]); + }; + + return { + setSimulatePipelineResponse, + }; +}; + +export const initHttpRequests = () => { + const server = sinon.fakeServer.create(); + + server.respondImmediately = true; + + // Define default response for unhandled requests. + // We make requests to APIs which don't impact the component under test, e.g. UI metric telemetry, + // and we can mock them all with a 200 instead of mocking each one individually. + server.respondWith([200, {}, 'DefaultSinonMockServerResponse']); + + const httpRequestsMockHelpers = registerHttpRequestMockHelpers(server); + + return { + server, + httpRequestsMockHelpers, + }; +}; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx new file mode 100644 index 0000000000000..fec3259fa019b --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.helpers.tsx @@ -0,0 +1,231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { act } from 'react-dom/test-utils'; +import React from 'react'; +import axios from 'axios'; +import axiosXhrAdapter from 'axios/lib/adapters/xhr'; + +import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mocks'; + +import { LocationDescriptorObject } from 'history'; +import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; +/* eslint-disable @kbn/eslint/no-restricted-paths */ +import { usageCollectionPluginMock } from 'src/plugins/usage_collection/public/mocks'; + +import { registerTestBed, TestBed } from '../../../../../../../test_utils'; +import { stubWebWorker } from '../../../../../../../test_utils/stub_web_worker'; + +import { + breadcrumbService, + uiMetricService, + documentationService, + apiService, +} from '../../../services'; + +import { + ProcessorsEditorContextProvider, + Props, + GlobalOnFailureProcessorsEditor, + ProcessorsEditor, +} from '../'; +import { TestPipelineActions } from '../'; + +import { initHttpRequests } from './http_requests.helpers'; + +stubWebWorker(); + +jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => { + const original = jest.requireActual('../../../../../../../../src/plugins/kibana_react/public'); + return { + ...original, + // Mocking CodeEditor, which uses React Monaco under the hood + CodeEditor: (props: any) => ( + { + props.onChange(e.jsonContent); + }} + /> + ), + }; +}); + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + // Mocking EuiCodeEditor, which uses React Ace under the hood + EuiCodeEditor: (props: any) => ( + { + props.onChange(syntheticEvent.jsonString); + }} + /> + ), + }; +}); + +jest.mock('react-virtualized', () => { + const original = jest.requireActual('react-virtualized'); + + return { + ...original, + AutoSizer: ({ children }: { children: any }) => ( +
{children({ height: 500, width: 500 })}
+ ), + }; +}); + +const history = scopedHistoryMock.create(); +history.createHref.mockImplementation((location: LocationDescriptorObject) => { + return `${location.pathname}?${location.search}`; +}); + +const appServices = { + breadcrumbs: breadcrumbService, + metric: uiMetricService, + documentation: documentationService, + api: apiService, + notifications: notificationServiceMock.createSetupContract(), + history, + uiSettings: {}, +}; + +const testBedSetup = registerTestBed( + (props: Props) => ( + + + + + + + + ), + { + doMountAsync: false, + } +); + +export interface SetupResult extends TestBed { + actions: ReturnType; +} + +const createActions = (testBed: TestBed) => { + const { find, component, form } = testBed; + + return { + clickAddDocumentsButton() { + act(() => { + find('addDocumentsButton').simulate('click'); + }); + component.update(); + }, + + async clickViewOutputButton() { + await act(async () => { + find('viewOutputButton').simulate('click'); + }); + component.update(); + }, + + closeTestPipelineFlyout() { + act(() => { + find('euiFlyoutCloseButton').simulate('click'); + }); + component.update(); + }, + + clickProcessorOutputTab() { + act(() => { + find('outputTab').simulate('click'); + }); + component.update(); + }, + + async clickRefreshOutputButton() { + await act(async () => { + find('refreshOutputButton').simulate('click'); + }); + component.update(); + }, + + async clickRunPipelineButton() { + await act(async () => { + find('runPipelineButton').simulate('click'); + }); + component.update(); + }, + + async toggleVerboseSwitch() { + await act(async () => { + form.toggleEuiSwitch('verboseOutputToggle'); + }); + component.update(); + }, + + addDocumentsJson(jsonString: string) { + find('documentsEditor').simulate('change', { + jsonString, + }); + }, + + async clickProcessor(processorSelector: string) { + await act(async () => { + find(`${processorSelector}.manageItemButton`).simulate('click'); + }); + component.update(); + }, + }; +}; + +export const setup = async (props: Props): Promise => { + const testBed = await testBedSetup(props); + return { + ...testBed, + actions: createActions(testBed), + }; +}; + +const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); + +export const setupEnvironment = () => { + // Initialize mock services + uiMetricService.setup(usageCollectionPluginMock.createSetupContract()); + // @ts-ignore + apiService.setup(mockHttpClient, uiMetricService); + + const { server, httpRequestsMockHelpers } = initHttpRequests(); + + return { + server, + httpRequestsMockHelpers, + }; +}; + +type TestSubject = + | 'addDocumentsButton' + | 'testPipelineFlyout' + | 'documentsDropdown' + | 'outputTab' + | 'documentsEditor' + | 'runPipelineButton' + | 'documentsTabContent' + | 'outputTabContent' + | 'verboseOutputToggle' + | 'refreshOutputButton' + | 'viewOutputButton' + | 'pipelineExecutionError' + | 'euiFlyoutCloseButton' + | 'processorStatusIcon' + | 'documentsTab' + | 'manageItemButton' + | 'processorSettingsForm' + | 'configurationTab' + | 'outputTab' + | 'processorOutputTabContent' + | string; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx new file mode 100644 index 0000000000000..339c840bb86f1 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/test_pipeline.test.tsx @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Pipeline } from '../../../../../common/types'; + +import { VerboseTestOutput, Document } from '../types'; +import { setup, SetupResult, setupEnvironment } from './test_pipeline.helpers'; +import { DOCUMENTS, SIMULATE_RESPONSE, PROCESSORS } from './constants'; + +interface ReqBody { + documents: Document[]; + verbose?: boolean; + pipeline: Pick; +} + +describe('Test pipeline', () => { + let onUpdate: jest.Mock; + let testBed: SetupResult; + + const { server, httpRequestsMockHelpers } = setupEnvironment(); + + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + server.restore(); + jest.useRealTimers(); + }); + + beforeEach(async () => { + onUpdate = jest.fn(); + testBed = await setup({ + value: { + ...PROCESSORS, + }, + onFlyoutOpen: jest.fn(), + onUpdate, + }); + }); + + describe('Test pipeline actions', () => { + it('should successfully add sample documents and execute the pipeline', async () => { + const { find, actions, exists } = testBed; + + httpRequestsMockHelpers.setSimulatePipelineResponse(SIMULATE_RESPONSE); + + // Flyout and document dropdown should not be visible + expect(exists('testPipelineFlyout')).toBe(false); + expect(exists('documentsDropdown')).toBe(false); + + // Open flyout + actions.clickAddDocumentsButton(); + + // Flyout should be visible with output tab initially disabled + expect(exists('testPipelineFlyout')).toBe(true); + expect(exists('documentsTabContent')).toBe(true); + expect(exists('outputTabContent')).toBe(false); + expect(find('outputTab').props().disabled).toEqual(true); + + // Add sample documents and click run + actions.addDocumentsJson(JSON.stringify(DOCUMENTS)); + await actions.clickRunPipelineButton(); + + // Verify request + const latestRequest = server.requests[server.requests.length - 1]; + const requestBody: ReqBody = JSON.parse(JSON.parse(latestRequest.requestBody).body); + const { + documents: reqDocuments, + verbose: reqVerbose, + pipeline: { processors: reqProcessors }, + } = requestBody; + + expect(reqDocuments).toEqual(DOCUMENTS); + expect(reqVerbose).toEqual(true); + + // We programatically add a unique tag field when calling the simulate API + // We do not know this value in the test, so we simply check that the field exists + // and only verify the processor configuration + reqProcessors.forEach((processor, index) => { + Object.entries(processor).forEach(([key, value]) => { + const { tag, ...config } = value; + expect(tag).toBeDefined(); + expect(config).toEqual(PROCESSORS.processors[index][key]); + }); + }); + + // Verify output tab is active + expect(find('outputTab').props().disabled).toEqual(false); + expect(exists('documentsTabContent')).toBe(false); + expect(exists('outputTabContent')).toBe(true); + + // Click reload button and verify request + const totalRequests = server.requests.length; + await actions.clickRefreshOutputButton(); + expect(server.requests.length).toBe(totalRequests + 1); + expect(server.requests[server.requests.length - 1].url).toBe( + '/api/ingest_pipelines/simulate' + ); + + // Click verbose toggle and verify request + await actions.toggleVerboseSwitch(); + expect(server.requests.length).toBe(totalRequests + 2); + expect(server.requests[server.requests.length - 1].url).toBe( + '/api/ingest_pipelines/simulate' + ); + }); + + test('should enable the output tab if cached documents exist', async () => { + const { actions, exists } = testBed; + + httpRequestsMockHelpers.setSimulatePipelineResponse(SIMULATE_RESPONSE); + + // Open flyout + actions.clickAddDocumentsButton(); + + // Add sample documents and click run + actions.addDocumentsJson(JSON.stringify(DOCUMENTS)); + await actions.clickRunPipelineButton(); + + // Close flyout + actions.closeTestPipelineFlyout(); + expect(exists('testPipelineFlyout')).toBe(false); + expect(exists('addDocumentsButton')).toBe(false); + expect(exists('documentsDropdown')).toBe(true); + + // Reopen flyout and verify output tab is enabled + await actions.clickViewOutputButton(); + expect(exists('testPipelineFlyout')).toBe(true); + expect(exists('documentsTabContent')).toBe(false); + expect(exists('outputTabContent')).toBe(true); + }); + + test('should surface API errors from the request', async () => { + const { actions, find, exists } = testBed; + + const error = { + status: 400, + error: 'Bad Request', + message: + '"[parse_exception] [_source] required property is missing, with { property_name="_source" }"', + }; + + httpRequestsMockHelpers.setSimulatePipelineResponse(undefined, { body: error }); + + // Open flyout + actions.clickAddDocumentsButton(); + + // Add invalid sample documents array and run the pipeline + actions.addDocumentsJson(JSON.stringify([{}])); + await actions.clickRunPipelineButton(); + + // Verify error rendered + expect(exists('pipelineExecutionError')).toBe(true); + expect(find('pipelineExecutionError').text()).toContain(error.message); + }); + }); + + describe('Processors', () => { + // This is a hack + // We need to provide the processor id in the mocked output; + // this is generated dynamically and not something we can stub. + // As a workaround, the value is added as a data attribute in the UI + // and we retrieve it to generate the mocked output. + const addProcessorTagtoMockOutput = (output: VerboseTestOutput) => { + const { find } = testBed; + + const docs = output.docs.map((doc) => { + const results = doc.processor_results.map((result, index) => { + const tag = find(`processors>${index}`).props()['data-processor-id']; + return { + ...result, + tag, + }; + }); + return { processor_results: results }; + }); + return { docs }; + }; + + it('should show "inactive" processor status by default', async () => { + const { find } = testBed; + + const statusIconLabel = find('processors>0.processorStatusIcon').props()['aria-label']; + + expect(statusIconLabel).toEqual('Not run'); + }); + + it('should update the processor status after execution', async () => { + const { actions, find } = testBed; + + const mockVerboseOutputWithProcessorTag = addProcessorTagtoMockOutput(SIMULATE_RESPONSE); + httpRequestsMockHelpers.setSimulatePipelineResponse(mockVerboseOutputWithProcessorTag); + + // Open flyout + actions.clickAddDocumentsButton(); + + // Add sample documents and click run + actions.addDocumentsJson(JSON.stringify(DOCUMENTS)); + await actions.clickRunPipelineButton(); + actions.closeTestPipelineFlyout(); + + // Verify status + const statusIconLabel = find('processors>0.processorStatusIcon').props()['aria-label']; + expect(statusIconLabel).toEqual('Success'); + }); + + describe('Output tab', () => { + beforeEach(async () => { + const { actions } = testBed; + + const mockVerboseOutputWithProcessorTag = addProcessorTagtoMockOutput(SIMULATE_RESPONSE); + httpRequestsMockHelpers.setSimulatePipelineResponse(mockVerboseOutputWithProcessorTag); + + // Add documents and run the pipeline + actions.clickAddDocumentsButton(); + actions.addDocumentsJson(JSON.stringify(DOCUMENTS)); + await actions.clickRunPipelineButton(); + actions.closeTestPipelineFlyout(); + }); + + it('should show the output of the processor', async () => { + const { actions, exists } = testBed; + + // Click processor to open manage flyout + await actions.clickProcessor('processors>0'); + // Verify flyout opened + expect(exists('processorSettingsForm')).toBe(true); + + // Navigate to "Output" tab + actions.clickProcessorOutputTab(); + // Verify content + expect(exists('processorOutputTabContent')).toBe(true); + }); + }); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx index e9aa5c1d56f73..e26b6a2890fe4 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx @@ -62,6 +62,7 @@ export const DocumentsDropdown: FunctionComponent = ({ updateSelectedDocument(Number(e.target.value)); }} aria-label={i18nTexts.ariaLabel} + data-test-subj="documentsDropdown" /> diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx index c081f69fd41fe..c30fdad969b24 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx @@ -91,7 +91,7 @@ export const ProcessorOutput: React.FunctionComponent = ({ } = processorOutput!; return ( - <> +

{i18nTexts.tabDescription}

@@ -212,6 +212,6 @@ export const ProcessorOutput: React.FunctionComponent = ({ )} - +
); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx index 4a67e27d2ebe6..bf69f817183ab 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx @@ -141,6 +141,7 @@ export const PipelineProcessorsEditorItem: FunctionComponent = memo( alignItems="center" justifyContent="spaceBetween" data-test-subj={selectorToDataTestSubject(selector)} + data-processor-id={processor.id} > diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx index 26ff113b97440..a58d482022b4d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item_status.tsx @@ -79,7 +79,13 @@ export const PipelineProcessorsItemStatus: FunctionComponent = ({ process return ( {label}

}> - +
); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx index 361e32c77d59b..6fd1adad54f84 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_output_button.tsx @@ -37,7 +37,7 @@ export const TestOutputButton: FunctionComponent = ({ @@ -51,7 +51,7 @@ export const TestOutputButton: FunctionComponent = ({ {i18nTexts.buttonLabel} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx index e8bb1aa1d357f..b26c6f536366d 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout.tsx @@ -182,6 +182,7 @@ export const TestPipelineFlyout: React.FunctionComponent = ({ } color="danger" iconType="alert" + data-test-subj="pipelineExecutionError" >

{testingError.message}

diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx index 8968416683c3e..dd12cdab0c934 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_documents.tsx @@ -72,7 +72,7 @@ export const DocumentsTab: React.FunctionComponent = ({ }); return ( - <> +

= ({ path="documents" component={JsonEditorField} componentProps={{ - ['data-test-subj']: 'documentsField', euiCodeEditorProps: { + 'data-test-subj': 'documentsEditor', height: '300px', 'aria-label': i18n.translate( 'xpack.ingestPipelines.testPipelineFlyout.documentsTab.editorFieldAriaLabel', @@ -128,6 +128,7 @@ export const DocumentsTab: React.FunctionComponent = ({ = ({ )} - +

); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx index 586fc9e60017a..926bab6da993c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/tab_output.tsx @@ -56,7 +56,7 @@ export const OutputTab: React.FunctionComponent = ({ } return ( - <> +

= ({ } checked={isVerboseEnabled} onChange={(e) => onEnableVerbose(e.target.checked)} + data-test-subj="verboseOutputToggle" /> @@ -88,6 +89,7 @@ export const OutputTab: React.FunctionComponent = ({ handleTestPipeline({ documents: cachedDocuments!, verbose: isVerboseEnabled }) } iconType="refresh" + data-test-subj="refreshOutputButton" > = ({ {content} - +

); }; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx index d0ea226e8db80..abfb86c2afda1 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_flyout_tabs/test_pipeline_tabs.tsx @@ -50,7 +50,7 @@ export const Tabs: React.FunctionComponent = ({ isSelected={tab.id === selectedTab} key={tab.id} disabled={getIsDisabled(tab.id)} - data-test-subj={tab.id.toLowerCase() + '_tab'} + data-test-subj={tab.id.toLowerCase() + 'Tab'} > {tab.name} diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.test.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.test.ts index 9b7c2069fcddd..a70c0d281cf95 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.test.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/deserialize.test.ts @@ -4,71 +4,143 @@ * you may not use this file except in compliance with the Elastic License. */ -import { deserialize } from './deserialize'; +import { deserialize, deserializeVerboseTestOutput } from './deserialize'; -describe('deserialize', () => { - it('tolerates certain bad values correctly', () => { - expect( - deserialize({ +describe('Deserialization', () => { + describe('deserialize()', () => { + it('tolerates certain bad values correctly', () => { + expect( + deserialize({ + processors: [ + { set: { field: 'test', value: 123 } }, + { badType1: null } as any, + { badType2: 1 } as any, + ], + onFailure: [ + { + gsub: { + field: '_index', + pattern: '(.monitoring-\\w+-)6(-.+)', + replacement: '$17$2', + }, + }, + ], + }) + ).toEqual({ processors: [ - { set: { field: 'test', value: 123 } }, - { badType1: null } as any, - { badType2: 1 } as any, + { + id: expect.any(String), + type: 'set', + options: { + field: 'test', + value: 123, + }, + }, + { + id: expect.any(String), + onFailure: undefined, + type: 'badType1', + options: {}, + }, + { + id: expect.any(String), + onFailure: undefined, + type: 'badType2', + options: {}, + }, ], onFailure: [ { - gsub: { + id: expect.any(String), + type: 'gsub', + onFailure: undefined, + options: { field: '_index', pattern: '(.monitoring-\\w+-)6(-.+)', replacement: '$17$2', }, }, ], - }) - ).toEqual({ - processors: [ + }); + }); + + it('throws for unacceptable values', () => { + expect(() => { + deserialize({ + processors: [{ reallyBad: undefined } as any, 1 as any], + onFailure: [], + }); + }).toThrow('Invalid processor type'); + }); + }); + + describe('deserializeVerboseOutput()', () => { + it('deserializes the verbose output of a simulated pipeline', () => { + expect( + deserializeVerboseTestOutput({ + docs: [ + { + processor_results: [ + { + doc: { + _id: 'id1', + _source: { + name: 'foo', + foo: 'bar', + }, + }, + processor_type: 'set', + status: 'success', + tag: 'e457615c-69c9-4d14-9e85-c477ad96e60f', + }, + ], + }, + { + processor_results: [ + { + doc: { + _id: 'id2', + _source: { + name: 'baz', + foo: 'bar', + }, + }, + processor_type: 'set', + status: 'success', + tag: 'e457615c-69c9-4d14-9e85-c477ad96e60f', + }, + ], + }, + ], + }) + ).toEqual([ { - id: expect.any(String), - type: 'set', - options: { - field: 'test', - value: 123, + 'e457615c-69c9-4d14-9e85-c477ad96e60f': { + doc: { + _id: 'id1', + _source: { + name: 'foo', + foo: 'bar', + }, + }, + processor_type: 'set', + status: 'success', }, }, { - id: expect.any(String), - onFailure: undefined, - type: 'badType1', - options: {}, - }, - { - id: expect.any(String), - onFailure: undefined, - type: 'badType2', - options: {}, - }, - ], - onFailure: [ - { - id: expect.any(String), - type: 'gsub', - onFailure: undefined, - options: { - field: '_index', - pattern: '(.monitoring-\\w+-)6(-.+)', - replacement: '$17$2', + 'e457615c-69c9-4d14-9e85-c477ad96e60f': { + doc: { + _id: 'id2', + _source: { + name: 'baz', + foo: 'bar', + }, + }, + processor_type: 'set', + status: 'success', }, }, - ], + ]); }); }); - - it('throws for unacceptable values', () => { - expect(() => { - deserialize({ - processors: [{ reallyBad: undefined } as any, 1 as any], - onFailure: [], - }); - }).toThrow('Invalid processor type'); - }); }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts index 9083985b0ff2e..5229f5eb0bb21 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/types.ts @@ -90,7 +90,7 @@ export type ProcessorStatus = export interface ProcessorResult { processor_type: string; status: ProcessorStatus; - doc: Document; + doc?: Document; tag: string; ignored_error?: any; error?: any;