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;