diff --git a/packages/kbn-utils/package.json b/packages/kbn-utils/package.json
index 2ad742650f74d..15fe5c6df5648 100644
--- a/packages/kbn-utils/package.json
+++ b/packages/kbn-utils/package.json
@@ -10,7 +10,8 @@
"kbn:watch": "yarn build --watch"
},
"dependencies": {
- "@kbn/config-schema": "1.0.0"
+ "@kbn/config-schema": "1.0.0",
+ "load-json-file": "^6.2.0"
},
"devDependencies": {
"typescript": "4.0.2"
diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
index d235bfe9d6fbc..24649a52b729b 100644
--- a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
+++ b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile
@@ -85,7 +85,6 @@ RUN groupadd --gid 1000 kibana && \
useradd --uid 1000 --gid 1000 \
--home-dir /usr/share/kibana --no-create-home \
kibana
-USER kibana
LABEL org.label-schema.build-date="{{dockerBuildDate}}" \
org.label-schema.license="{{license}}" \
@@ -115,8 +114,13 @@ LABEL name="Kibana" \
release="1" \
summary="Kibana" \
description="Your window into the Elastic Stack."
+
+RUN mkdir /licenses && \
+ cp LICENSE.txt /licenses/LICENSE
{{/ubi}}
+USER kibana
+
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
CMD ["/usr/local/bin/kibana-docker"]
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx
index 4053f2f4bb613..623e6e47167d2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx
@@ -6,7 +6,7 @@
import React from 'react';
import { shallow } from 'enzyme';
-import { EuiPageSideBar, EuiButton } from '@elastic/eui';
+import { EuiPageSideBar, EuiButton, EuiPageBody } from '@elastic/eui';
import { Layout, INavContext } from './layout';
@@ -15,6 +15,13 @@ describe('Layout', () => {
const wrapper = shallow( );
expect(wrapper.find('.enterpriseSearchLayout')).toHaveLength(1);
+ expect(wrapper.find(EuiPageBody).prop('restrictWidth')).toBeFalsy();
+ });
+
+ it('passes the restrictWidth prop', () => {
+ const wrapper = shallow( );
+
+ expect(wrapper.find(EuiPageBody).prop('restrictWidth')).toEqual(true);
});
it('renders navigation', () => {
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx
index b4497140b65b7..e122c4d5cfdfa 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx
@@ -14,6 +14,7 @@ import './layout.scss';
interface ILayoutProps {
navigation: React.ReactNode;
+ restrictWidth?: boolean;
}
export interface INavContext {
@@ -21,7 +22,7 @@ export interface INavContext {
}
export const NavContext = React.createContext({});
-export const Layout: React.FC = ({ children, navigation }) => {
+export const Layout: React.FC = ({ children, navigation, restrictWidth }) => {
const [isNavOpen, setIsNavOpen] = useState(false);
const toggleNavigation = () => setIsNavOpen(!isNavOpen);
const closeNavigation = () => setIsNavOpen(false);
@@ -54,7 +55,9 @@ export const Layout: React.FC = ({ children, navigation }) => {
{navigation}
- {children}
+
+ {children}
+
);
};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
index 2e7a47e0c93de..e46e5156e30f3 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.helpers.tsx
@@ -126,7 +126,7 @@ const createActions = (testBed: TestBed) => {
});
});
await act(async () => {
- find('processorSettingsForm.submitButton').simulate('click');
+ find('addProcessorForm.submitButton').simulate('click');
});
},
@@ -166,7 +166,7 @@ const createActions = (testBed: TestBed) => {
});
});
await act(async () => {
- find('processorSettingsForm.submitButton').simulate('click');
+ find('addProcessorForm.submitButton').simulate('click');
});
},
@@ -202,8 +202,10 @@ type TestSubject =
| 'pipelineEditorDoneButton'
| 'pipelineEditorOnFailureToggle'
| 'addProcessorsButtonLevel1'
- | 'processorSettingsForm'
- | 'processorSettingsForm.submitButton'
+ | 'editProcessorForm'
+ | 'editProcessorForm.submitButton'
+ | 'addProcessorForm.submitButton'
+ | 'addProcessorForm'
| 'processorOptionsEditor'
| 'processorSettingsFormFlyout'
| 'processorTypeSelector'
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
index 38c652f41e5e1..74ae8b8894b9f 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/__jest__/pipeline_processors_editor.test.tsx
@@ -180,7 +180,7 @@ describe('Pipeline Editor', () => {
it('prevents moving a processor while in edit mode', () => {
const { find, exists } = testBed;
find('processors>0.manageItemButton').simulate('click');
- expect(exists('processorSettingsForm')).toBe(true);
+ expect(exists('editProcessorForm')).toBe(true);
expect(find('processors>0.moveItemButton').props().disabled).toBe(true);
expect(find('processors>1.moveItemButton').props().disabled).toBe(true);
});
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
index fec3259fa019b..f4c89d7a1058a 100644
--- 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
@@ -140,8 +140,8 @@ const createActions = (testBed: TestBed) => {
component.update();
},
- clickProcessorOutputTab() {
- act(() => {
+ async clickProcessorOutputTab() {
+ await act(async () => {
find('outputTab').simulate('click');
});
component.update();
@@ -224,7 +224,8 @@ type TestSubject =
| 'processorStatusIcon'
| 'documentsTab'
| 'manageItemButton'
- | 'processorSettingsForm'
+ | 'addProcessorForm'
+ | 'editProcessorForm'
| 'configurationTab'
| 'outputTab'
| 'processorOutputTabContent'
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
index 339c840bb86f1..e5118a6e465af 100644
--- 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
@@ -44,7 +44,7 @@ describe('Test pipeline', () => {
describe('Test pipeline actions', () => {
it('should successfully add sample documents and execute the pipeline', async () => {
- const { find, actions, exists } = testBed;
+ const { actions, exists } = testBed;
httpRequestsMockHelpers.setSimulatePipelineResponse(SIMULATE_RESPONSE);
@@ -59,7 +59,6 @@ describe('Test pipeline', () => {
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));
@@ -89,21 +88,25 @@ describe('Test pipeline', () => {
});
// 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);
+ // There will be two requests made to the simulate API
+ // the second request will have verbose enabled to update the processor results
+ expect(server.requests.length).toBe(totalRequests + 2);
+ expect(server.requests[server.requests.length - 2].url).toBe(
+ '/api/ingest_pipelines/simulate'
+ );
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.length).toBe(totalRequests + 3);
expect(server.requests[server.requests.length - 1].url).toBe(
'/api/ingest_pipelines/simulate'
);
@@ -228,10 +231,10 @@ describe('Test pipeline', () => {
// Click processor to open manage flyout
await actions.clickProcessor('processors>0');
// Verify flyout opened
- expect(exists('processorSettingsForm')).toBe(true);
+ expect(exists('editProcessorForm')).toBe(true);
// Navigate to "Output" tab
- actions.clickProcessorOutputTab();
+ await 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.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.scss
deleted file mode 100644
index c5b14dc129b0e..0000000000000
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.documentsDropdown__selectContainer {
- max-width: 200px;
-}
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
deleted file mode 100644
index e26b6a2890fe4..0000000000000
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/documents_dropdown.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 { i18n } from '@kbn/i18n';
-import React, { FunctionComponent } from 'react';
-import { EuiSelect, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
-
-import { Document } from '../../types';
-
-import './documents_dropdown.scss';
-
-const i18nTexts = {
- ariaLabel: i18n.translate(
- 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsDropdownAriaLabel',
- {
- defaultMessage: 'Select documents',
- }
- ),
- dropdownLabel: i18n.translate(
- 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsdropdownLabel',
- {
- defaultMessage: 'Documents:',
- }
- ),
- buttonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.testPipeline.buttonLabel', {
- defaultMessage: 'Add documents',
- }),
-};
-
-const getDocumentOptions = (documents: Document[]) =>
- documents.map((doc, index) => ({
- value: index,
- text: doc._id,
- }));
-
-interface Props {
- documents: Document[];
- selectedDocumentIndex: number;
- updateSelectedDocument: (index: number) => void;
-}
-
-export const DocumentsDropdown: FunctionComponent = ({
- documents,
- selectedDocumentIndex,
- updateSelectedDocument,
-}) => {
- return (
-
-
-
- {i18nTexts.dropdownLabel}
-
-
-
- {
- 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/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts
index 435d0ed66c4b0..d476202aa43bb 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/index.ts
@@ -4,11 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export {
- ManageProcessorForm,
- ManageProcessorFormOnSubmitArg,
- OnSubmitHandler,
-} from './manage_processor_form';
+export { ProcessorForm, ProcessorFormOnSubmitArg, OnSubmitHandler } from './processor_form';
export { ProcessorsTree, ProcessorInfo, OnActionHandler } from './processors_tree';
@@ -22,6 +18,4 @@ export { OnDoneLoadJsonHandler, LoadFromJsonButton } from './load_from_json';
export { TestPipelineActions } from './test_pipeline';
-export { DocumentsDropdown } from './documents_dropdown';
-
export { PipelineProcessorsItemTooltip, Position } from './pipeline_processors_editor_item_tooltip';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx
deleted file mode 100644
index 083529921b0a7..0000000000000
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.container.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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 React, { FunctionComponent, useCallback, useEffect } from 'react';
-
-import { useForm, OnFormUpdateArg, FormData, useKibana } from '../../../../../shared_imports';
-import { ProcessorInternal } from '../../types';
-
-import { ManageProcessorForm as ViewComponent } from './manage_processor_form';
-
-export type ManageProcessorFormOnSubmitArg = Omit;
-
-export type OnSubmitHandler = (processor: ManageProcessorFormOnSubmitArg) => void;
-
-export type OnFormUpdateHandler = (form: OnFormUpdateArg) => void;
-
-interface Props {
- onFormUpdate: OnFormUpdateHandler;
- onSubmit: OnSubmitHandler;
- isOnFailure: boolean;
- onOpen: () => void;
- onClose: () => void;
- processor?: ProcessorInternal;
-}
-
-export const ManageProcessorForm: FunctionComponent = ({
- processor,
- onFormUpdate,
- onSubmit,
- ...rest
-}) => {
- const { services } = useKibana();
-
- const handleSubmit = useCallback(
- async (data: FormData, isValid: boolean) => {
- if (isValid) {
- const { type, customOptions, fields } = data;
- onSubmit({
- type,
- options: customOptions ? customOptions : fields,
- });
- }
- },
- [onSubmit]
- );
-
- const maybeProcessorOptions = processor?.options;
- const { form } = useForm({
- defaultValue: { fields: maybeProcessorOptions ?? {} },
- onSubmit: handleSubmit,
- });
-
- useEffect(() => {
- const subscription = form.subscribe(onFormUpdate);
- return subscription.unsubscribe;
-
- // TODO: Address this issue
- // For some reason adding `form` object to the dependencies array here is causing an
- // infinite update loop.
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [onFormUpdate]);
-
- return (
-
- );
-};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.tsx
deleted file mode 100644
index ee8ca71e58446..0000000000000
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/manage_processor_form.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * 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 { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
-import React, { FunctionComponent, memo, useEffect, useState } from 'react';
-import {
- EuiButton,
- EuiButtonEmpty,
- EuiFlyout,
- EuiFlyoutHeader,
- EuiFlyoutBody,
- EuiFlyoutFooter,
- EuiTabs,
- EuiTab,
- EuiTitle,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
-} from '@elastic/eui';
-
-import { Form, FormDataProvider, FormHook } from '../../../../../shared_imports';
-import { ProcessorInternal } from '../../types';
-import { useTestPipelineContext } from '../../context';
-import { getProcessorDescriptor } from '../shared';
-
-import { ProcessorSettingsFields } from './processor_settings_fields';
-import { DocumentationButton } from './documentation_button';
-import { ProcessorOutput } from './processor_output';
-
-export interface Props {
- isOnFailure: boolean;
- processor?: ProcessorInternal;
- form: FormHook;
- onClose: () => void;
- onOpen: () => void;
- esDocsBasePath: string;
-}
-
-const updateButtonLabel = i18n.translate(
- 'xpack.ingestPipelines.settingsFormOnFailureFlyout.updateButtonLabel',
- { defaultMessage: 'Update' }
-);
-
-const addButtonLabel = i18n.translate(
- 'xpack.ingestPipelines.settingsFormOnFailureFlyout.addButtonLabel',
- { defaultMessage: 'Add' }
-);
-
-const cancelButtonLabel = i18n.translate(
- 'xpack.ingestPipelines.settingsFormOnFailureFlyout.cancelButtonLabel',
- { defaultMessage: 'Cancel' }
-);
-
-export type TabType = 'configuration' | 'output';
-
-interface Tab {
- id: TabType;
- name: string;
-}
-
-const tabs: Tab[] = [
- {
- id: 'configuration',
- name: i18n.translate(
- 'xpack.ingestPipelines.settingsFormOnFailureFlyout.configurationTabTitle',
- {
- defaultMessage: 'Configuration',
- }
- ),
- },
- {
- id: 'output',
- name: i18n.translate('xpack.ingestPipelines.settingsFormOnFailureFlyout.outputTabTitle', {
- defaultMessage: 'Output',
- }),
- },
-];
-
-const getFlyoutTitle = (isOnFailure: boolean, isExistingProcessor: boolean) => {
- if (isExistingProcessor) {
- return isOnFailure ? (
-
- ) : (
-
- );
- }
-
- return isOnFailure ? (
-
- ) : (
-
- );
-};
-
-export const ManageProcessorForm: FunctionComponent = memo(
- ({ processor, form, isOnFailure, onClose, onOpen, esDocsBasePath }) => {
- const { testPipelineData, setCurrentTestPipelineData } = useTestPipelineContext();
- const {
- testOutputPerProcessor,
- config: { selectedDocumentIndex, documents },
- } = testPipelineData;
-
- const processorOutput =
- processor &&
- testOutputPerProcessor &&
- testOutputPerProcessor[selectedDocumentIndex][processor.id];
-
- const updateSelectedDocument = (index: number) => {
- setCurrentTestPipelineData({
- type: 'updateActiveDocument',
- payload: {
- config: {
- selectedDocumentIndex: index,
- },
- },
- });
- };
-
- useEffect(
- () => {
- onOpen();
- },
- [] /* eslint-disable-line react-hooks/exhaustive-deps */
- );
-
- const [activeTab, setActiveTab] = useState('configuration');
-
- let flyoutContent: React.ReactNode;
-
- if (activeTab === 'output') {
- flyoutContent = (
-
- );
- } else {
- flyoutContent = ;
- }
-
- return (
-
- );
- },
- (previous, current) => {
- return previous.processor === current.processor;
- }
-);
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
deleted file mode 100644
index c30fdad969b24..0000000000000
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_output.tsx
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * 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 React from 'react';
-import { i18n } from '@kbn/i18n';
-
-import {
- EuiAccordion,
- EuiCallOut,
- EuiCodeBlock,
- EuiText,
- EuiFlexGroup,
- EuiFlexItem,
- EuiSpacer,
-} from '@elastic/eui';
-
-import { ProcessorResult, Document } from '../../types';
-import { DocumentsDropdown } from '../documents_dropdown';
-
-export interface Props {
- processorOutput?: ProcessorResult;
- documents: Document[];
- selectedDocumentIndex: number;
- updateSelectedDocument: (index: number) => void;
-}
-
-const i18nTexts = {
- noOutputCalloutTitle: i18n.translate(
- 'xpack.ingestPipelines.processorOutput.noOutputCalloutTitle',
- {
- defaultMessage: 'Unable to load the processor output.',
- }
- ),
- tabDescription: i18n.translate('xpack.ingestPipelines.processorOutput.descriptionText', {
- defaultMessage:
- 'View how the processor affects the ingest document as it passes through the pipeline.',
- }),
- skippedCalloutTitle: i18n.translate('xpack.ingestPipelines.processorOutput.skippedCalloutTitle', {
- defaultMessage: 'The processor was not run.',
- }),
- droppedCalloutTitle: i18n.translate('xpack.ingestPipelines.processorOutput.droppedCalloutTitle', {
- defaultMessage: 'The document was dropped.',
- }),
- processorOutputLabel: i18n.translate(
- 'xpack.ingestPipelines.processorOutput.processorOutputCodeBlockLabel',
- {
- defaultMessage: 'Processor output',
- }
- ),
- processorErrorLabel: i18n.translate(
- 'xpack.ingestPipelines.processorOutput.processorErrorCodeBlockLabel',
- {
- defaultMessage: 'Processor error',
- }
- ),
- prevProcessorLabel: i18n.translate(
- 'xpack.ingestPipelines.processorOutput.previousOutputCodeBlockLabel',
- {
- defaultMessage: 'View previous processor output',
- }
- ),
- processorIgnoredErrorLabel: i18n.translate(
- 'xpack.ingestPipelines.processorOutput.ignoredErrorCodeBlockLabel',
- {
- defaultMessage: 'View ignored error',
- }
- ),
-};
-
-export const ProcessorOutput: React.FunctionComponent = ({
- processorOutput,
- documents,
- selectedDocumentIndex,
- updateSelectedDocument,
-}) => {
- // This code should not be reached,
- // but if for some reason the output is undefined, we render a callout message
- if (!processorOutput) {
- return ;
- }
-
- const {
- prevProcessorResult,
- doc: currentResult,
- ignored_error: ignoredError,
- error,
- status,
- } = processorOutput!;
-
- return (
-
-
- {i18nTexts.tabDescription}
-
-
- {/* There is no output for "skipped" status, so we render an info callout */}
- {status === 'skipped' && (
- <>
-
-
- >
- )}
-
- {/* There is no output for "dropped status", so we render a warning callout */}
- {status === 'dropped' && (
- <>
-
-
- >
- )}
-
- {currentResult && (
- <>
-
-
-
-
- {i18nTexts.processorOutputLabel}
-
-
-
-
-
-
-
-
-
- {JSON.stringify(currentResult, null, 2)}
-
- >
- )}
-
- {error && (
- <>
-
-
-
-
- {i18nTexts.processorErrorLabel}
-
-
-
-
-
-
-
-
-
- {JSON.stringify(error, null, 2)}
-
- >
- )}
-
- {prevProcessorResult?.doc && (
- <>
-
-
-
- {i18nTexts.prevProcessorLabel}
-
- }
- >
- <>
-
-
-
- {JSON.stringify(prevProcessorResult.doc, null, 2)}
-
- >
-
- >
- )}
-
- {ignoredError && (
- <>
-
-
-
- {i18nTexts.processorIgnoredErrorLabel}
-
- }
- >
- <>
-
-
-
- {JSON.stringify(ignoredError, null, 2)}
-
- >
-
- >
- )}
-
- );
-};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss
index d9c3d84eec082..55630fa96d9b0 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.scss
@@ -63,6 +63,6 @@
&__statusContainer {
// Prevent content jump when spinner renders
- min-width: 12px;
+ min-width: 15px;
}
}
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 a58d482022b4d..08d456b47180c 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
@@ -6,11 +6,12 @@
import React, { FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiToolTip, EuiIcon } from '@elastic/eui';
+import { EuiToolTip, EuiIcon, IconType } from '@elastic/eui';
import { ProcessorStatus } from '../types';
+import { ErrorIcon, ErrorIgnoredIcon, SkippedIcon } from './shared';
interface ProcessorStatusIcon {
- icon: string;
+ icon: IconType;
iconColor: string;
label: string;
}
@@ -24,28 +25,28 @@ const processorStatusToIconMap: Record = {
}),
},
error: {
- icon: 'crossInACircleFilled',
+ icon: ErrorIcon,
iconColor: 'danger',
label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.errorStatusAriaLabel', {
defaultMessage: 'Error',
}),
},
error_ignored: {
- icon: 'alert',
- iconColor: 'warning',
+ icon: ErrorIgnoredIcon,
+ iconColor: 'danger',
label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.errorIgnoredStatusAriaLabel', {
defaultMessage: 'Error ignored',
}),
},
dropped: {
- icon: 'alert',
- iconColor: 'warning',
+ icon: 'indexClose',
+ iconColor: 'subdued',
label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.droppedStatusAriaLabel', {
defaultMessage: 'Dropped',
}),
},
skipped: {
- icon: 'dot',
+ icon: SkippedIcon,
iconColor: 'subdued',
label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.skippedStatusAriaLabel', {
defaultMessage: 'Skipped',
@@ -53,7 +54,7 @@ const processorStatusToIconMap: Record = {
},
inactive: {
icon: 'dot',
- iconColor: 'subdued',
+ iconColor: '#D3DAE6', // $euiColorLightShade
label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.inactiveStatusAriaLabel', {
defaultMessage: 'Not run',
}),
@@ -64,7 +65,7 @@ const processorStatusToIconMap: Record = {
// This is not expected and likely means we need to modify the code to support a new status
const unknownStatus = {
icon: 'dot',
- iconColor: 'subdued',
+ iconColor: '#D3DAE6', // $euiColorLightShade
label: i18n.translate('xpack.ingestPipelines.pipelineEditorItem.unknownStatusAriaLabel', {
defaultMessage: 'Unknown',
}),
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx
new file mode 100644
index 0000000000000..5231a3d17811b
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/add_processor_form.tsx
@@ -0,0 +1,134 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import React, { FunctionComponent, useEffect } from 'react';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFlyout,
+ EuiFlyoutHeader,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiTitle,
+ EuiFlexGroup,
+ EuiFlexItem,
+} from '@elastic/eui';
+
+import { Form, FormDataProvider, FormHook } from '../../../../../shared_imports';
+import { getProcessorDescriptor } from '../shared';
+
+import { DocumentationButton } from './documentation_button';
+import { ProcessorSettingsFields } from './processor_settings_fields';
+
+interface Fields {
+ fields: { [key: string]: any };
+}
+export interface Props {
+ isOnFailure: boolean;
+ form: FormHook;
+ onOpen: () => void;
+ esDocsBasePath: string;
+ closeFlyout: () => void;
+ handleSubmit: (shouldCloseFlyout?: boolean) => Promise;
+}
+
+const addButtonLabel = i18n.translate(
+ 'xpack.ingestPipelines.addProcessorFormOnFailureFlyout.addButtonLabel',
+ { defaultMessage: 'Add' }
+);
+
+const cancelButtonLabel = i18n.translate(
+ 'xpack.ingestPipelines.addProcesorFormOnFailureFlyout.cancelButtonLabel',
+ { defaultMessage: 'Cancel' }
+);
+
+const getFlyoutTitle = (isOnFailure: boolean) => {
+ return isOnFailure ? (
+
+ ) : (
+
+ );
+};
+
+export const AddProcessorForm: FunctionComponent = ({
+ isOnFailure,
+ onOpen,
+ form,
+ esDocsBasePath,
+ closeFlyout,
+ handleSubmit,
+}) => {
+ useEffect(
+ () => {
+ onOpen();
+ },
+ [] /* eslint-disable-line react-hooks/exhaustive-deps */
+ );
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/documentation_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/documentation_button.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/documentation_button.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/documentation_button.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx
new file mode 100644
index 0000000000000..e449ed75b6343
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/edit_processor_form.tsx
@@ -0,0 +1,253 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import React, { FunctionComponent, useEffect, useState } from 'react';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiFlyout,
+ EuiFlyoutHeader,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiTabs,
+ EuiTab,
+ EuiTitle,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+} from '@elastic/eui';
+
+import { Form, FormDataProvider, FormHook } from '../../../../../shared_imports';
+import { ProcessorInternal } from '../../types';
+import { useTestPipelineContext } from '../../context';
+import { getProcessorDescriptor } from '../shared';
+
+import { ProcessorSettingsFields } from './processor_settings_fields';
+import { DocumentationButton } from './documentation_button';
+import { ProcessorOutput } from './processor_output';
+import { Fields } from './processor_form.container';
+
+export interface Props {
+ isOnFailure: boolean;
+ form: FormHook;
+ onOpen: () => void;
+ esDocsBasePath: string;
+ closeFlyout: () => void;
+ resetProcessors: () => void;
+ handleSubmit: (shouldCloseFlyout?: boolean) => Promise;
+ getProcessor: () => ProcessorInternal;
+}
+
+const updateButtonLabel = i18n.translate(
+ 'xpack.ingestPipelines.processorFormFlyout.updateButtonLabel',
+ { defaultMessage: 'Update' }
+);
+
+const cancelButtonLabel = i18n.translate(
+ 'xpack.ingestPipelines.processorFormFlyout.cancelButtonLabel',
+ { defaultMessage: 'Cancel' }
+);
+
+export type TabType = 'configuration' | 'output';
+
+interface Tab {
+ id: TabType;
+ name: string;
+}
+
+const tabs: Tab[] = [
+ {
+ id: 'configuration',
+ name: i18n.translate(
+ 'xpack.ingestPipelines.settingsFormOnFailureFlyout.configurationTabTitle',
+ {
+ defaultMessage: 'Configuration',
+ }
+ ),
+ },
+ {
+ id: 'output',
+ name: i18n.translate('xpack.ingestPipelines.settingsFormOnFailureFlyout.outputTabTitle', {
+ defaultMessage: 'Output',
+ }),
+ },
+];
+
+const getFlyoutTitle = (isOnFailure: boolean) => {
+ return isOnFailure ? (
+
+ ) : (
+
+ );
+};
+
+export const EditProcessorForm: FunctionComponent = ({
+ getProcessor,
+ form,
+ isOnFailure,
+ onOpen,
+ esDocsBasePath,
+ closeFlyout,
+ handleSubmit,
+ resetProcessors,
+}) => {
+ const { testPipelineData, setCurrentTestPipelineData } = useTestPipelineContext();
+ const {
+ testOutputPerProcessor,
+ config: { selectedDocumentIndex, documents },
+ isExecutingPipeline,
+ } = testPipelineData;
+
+ const processor = getProcessor();
+
+ const processorOutput =
+ processor &&
+ testOutputPerProcessor &&
+ testOutputPerProcessor[selectedDocumentIndex][processor.id];
+
+ const updateSelectedDocument = (index: number) => {
+ setCurrentTestPipelineData({
+ type: 'updateActiveDocument',
+ payload: {
+ config: {
+ selectedDocumentIndex: index,
+ },
+ },
+ });
+ };
+
+ useEffect(
+ () => {
+ onOpen();
+ },
+ [] /* eslint-disable-line react-hooks/exhaustive-deps */
+ );
+
+ const [activeTab, setActiveTab] = useState('configuration');
+
+ let flyoutContent: React.ReactNode;
+
+ if (activeTab === 'output') {
+ flyoutContent = (
+
+ );
+ } else {
+ flyoutContent = ;
+ }
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/index.ts
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/index.ts
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/index.ts
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/text_editor.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/text_editor.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/text_editor.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/xjson_editor.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/xjson_editor.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/field_components/xjson_editor.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/field_components/xjson_editor.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/index.ts
similarity index 71%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/index.ts
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/index.ts
index 986bd52e911bf..5a8d2522f1376 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/index.ts
@@ -5,7 +5,7 @@
*/
export {
- ManageProcessorForm,
- ManageProcessorFormOnSubmitArg,
+ ProcessorFormContainer as ProcessorForm,
+ ProcessorFormOnSubmitArg,
OnSubmitHandler,
-} from './manage_processor_form.container';
+} from './processor_form.container';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx
new file mode 100644
index 0000000000000..332908d0756f2
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_form.container.tsx
@@ -0,0 +1,127 @@
+/*
+ * 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 React, { FunctionComponent, useCallback, useEffect, useRef } from 'react';
+
+import { useForm, OnFormUpdateArg, FormData, useKibana } from '../../../../../shared_imports';
+import { ProcessorInternal } from '../../types';
+
+import { EditProcessorForm } from './edit_processor_form';
+import { AddProcessorForm } from './add_processor_form';
+
+export type ProcessorFormOnSubmitArg = Omit;
+
+export type OnSubmitHandler = (processor: ProcessorFormOnSubmitArg) => void;
+
+export type OnFormUpdateHandler = (form: OnFormUpdateArg) => void;
+
+export interface Fields {
+ fields: { [key: string]: any };
+}
+
+interface Props {
+ onFormUpdate: OnFormUpdateHandler;
+ onSubmit: OnSubmitHandler;
+ isOnFailure: boolean;
+ onOpen: () => void;
+ onClose: () => void;
+ processor?: ProcessorInternal;
+}
+
+export const ProcessorFormContainer: FunctionComponent = ({
+ processor,
+ onFormUpdate,
+ onSubmit,
+ onClose,
+ ...rest
+}) => {
+ const { services } = useKibana();
+
+ // We need to keep track of the processor form state if the user
+ // has made config changes, navigated between tabs (Configuration vs. Output)
+ // and has not yet submitted the form
+ const unsavedFormState = useRef();
+
+ const getProcessor = useCallback((): ProcessorInternal => {
+ let options;
+
+ if (unsavedFormState?.current) {
+ options = unsavedFormState.current;
+ } else {
+ options = processor?.options ?? {};
+ }
+
+ return { ...processor, options } as ProcessorInternal;
+ }, [processor, unsavedFormState]);
+
+ const { form } = useForm({
+ defaultValue: { fields: getProcessor().options },
+ });
+
+ const handleSubmit = useCallback(
+ async (shouldCloseFlyout: boolean = true) => {
+ const { isValid, data } = await form.submit();
+
+ if (isValid) {
+ const { type, customOptions, fields } = data as FormData;
+ const options = customOptions ? customOptions : fields;
+
+ unsavedFormState.current = options;
+
+ onSubmit({
+ type,
+ options,
+ });
+
+ if (shouldCloseFlyout) {
+ onClose();
+ }
+ }
+ },
+ [form, onClose, onSubmit]
+ );
+
+ const resetProcessors = useCallback(() => {
+ onSubmit({
+ type: processor!.type,
+ options: processor?.options || {},
+ });
+ }, [onSubmit, processor]);
+
+ useEffect(() => {
+ const subscription = form.subscribe(onFormUpdate);
+ return subscription.unsubscribe;
+
+ // TODO: Address this issue
+ // For some reason adding `form` object to the dependencies array here is causing an
+ // infinite update loop.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [onFormUpdate]);
+
+ if (processor) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/index.ts
new file mode 100644
index 0000000000000..3b506fc9296e3
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/index.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export { ProcessorOutput } from './processor_output';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.scss
new file mode 100644
index 0000000000000..e1b5eb83584ff
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.scss
@@ -0,0 +1,12 @@
+.processorOutput {
+ &__callOut {
+ &--customIcon {
+ .euiCallOutHeader {
+ align-items: center;
+ }
+ }
+ &__codeBlock > pre {
+ background: transparent;
+ }
+ }
+}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.tsx
new file mode 100644
index 0000000000000..bd0ce6ca2cd52
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_output/processor_output.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 React, { FunctionComponent } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import {
+ EuiAccordion,
+ EuiCallOut,
+ EuiCodeBlock,
+ EuiText,
+ EuiSpacer,
+ EuiSelect,
+} from '@elastic/eui';
+
+import { SectionLoading } from '../../../../../../shared_imports';
+import { ProcessorResult, Document } from '../../../types';
+import { ErrorIcon, ErrorIgnoredIcon, SkippedIcon } from '../../shared';
+
+import './processor_output.scss';
+
+export interface Props {
+ processorOutput?: ProcessorResult;
+ documents: Document[];
+ selectedDocumentIndex: number;
+ updateSelectedDocument: (index: number) => void;
+ isExecuting?: boolean;
+}
+
+const i18nTexts = {
+ tabDescription: i18n.translate('xpack.ingestPipelines.processorOutput.descriptionText', {
+ defaultMessage:
+ 'View how the processor affects the ingest document as it passes through the pipeline.',
+ }),
+ skippedCalloutTitle: i18n.translate('xpack.ingestPipelines.processorOutput.skippedCalloutTitle', {
+ defaultMessage: 'The processor was not run.',
+ }),
+ droppedCalloutTitle: i18n.translate('xpack.ingestPipelines.processorOutput.droppedCalloutTitle', {
+ defaultMessage: 'The document was dropped.',
+ }),
+ noOutputCalloutTitle: i18n.translate(
+ 'xpack.ingestPipelines.processorOutput.noOutputCalloutTitle',
+ {
+ defaultMessage: 'Output is not available for this processor.',
+ }
+ ),
+ processorOutputLabel: i18n.translate(
+ 'xpack.ingestPipelines.processorOutput.processorOutputCodeBlockLabel',
+ {
+ defaultMessage: 'Data out',
+ }
+ ),
+ processorErrorTitle: i18n.translate(
+ 'xpack.ingestPipelines.processorOutput.processorErrorCodeBlockLabel',
+ {
+ defaultMessage: 'There was an error',
+ }
+ ),
+ prevProcessorLabel: i18n.translate(
+ 'xpack.ingestPipelines.processorOutput.processorInputCodeBlockLabel',
+ {
+ defaultMessage: 'Data in',
+ }
+ ),
+ processorIgnoredErrorTitle: i18n.translate(
+ 'xpack.ingestPipelines.processorOutput.ignoredErrorCodeBlockLabel',
+ {
+ defaultMessage: 'There was an error that was ignored',
+ }
+ ),
+ documentsDropdownLabel: i18n.translate(
+ 'xpack.ingestPipelines.processorOutput.documentsDropdownLabel',
+ {
+ defaultMessage: 'Test data:',
+ }
+ ),
+ loadingMessage: i18n.translate('xpack.ingestPipelines.processorOutput.loadingMessage', {
+ defaultMessage: 'Loading processor output…',
+ }),
+};
+
+export const ProcessorOutput: FunctionComponent = ({
+ processorOutput,
+ documents,
+ selectedDocumentIndex,
+ updateSelectedDocument,
+ isExecuting,
+}) => {
+ if (isExecuting) {
+ return {i18nTexts.loadingMessage} ;
+ }
+
+ if (!processorOutput) {
+ return ;
+ }
+
+ const {
+ processorInput,
+ doc: currentResult,
+ ignored_error: ignoredError,
+ error,
+ status,
+ } = processorOutput!;
+
+ const NoOutputCallOut: FunctionComponent = () => (
+
+ );
+
+ const getOutputContent = () => {
+ switch (status) {
+ case 'skipped':
+ return (
+
+ );
+ case 'dropped':
+ return ;
+ case 'success':
+ if (currentResult) {
+ return (
+
+ {JSON.stringify(currentResult, null, 2)}
+
+ );
+ }
+
+ return ;
+ case 'error':
+ return (
+
+
+ {JSON.stringify(error, null, 2)}
+
+
+ );
+ case 'error_ignored':
+ return (
+
+
+ {JSON.stringify(ignoredError, null, 2)}
+
+
+ );
+ default:
+ return ;
+ }
+ };
+
+ return (
+
+
+ {i18nTexts.tabDescription}
+
+
+
+
+ {/* Documents dropdown */}
+
({
+ value: index,
+ text: i18n.translate('xpack.ingestPipelines.processorOutput.documentLabel', {
+ defaultMessage: 'Document {number}',
+ values: {
+ number: index + 1,
+ },
+ }),
+ }))}
+ value={selectedDocumentIndex}
+ onChange={(e) => {
+ updateSelectedDocument(Number(e.target.value));
+ }}
+ aria-label={i18nTexts.documentsDropdownLabel}
+ prepend={i18nTexts.documentsDropdownLabel}
+ />
+
+
+
+ {/* Data-in accordion */}
+
+ {i18nTexts.prevProcessorLabel}
+
+ }
+ >
+ <>
+
+
+
+ {/* If there is no processorInput defined (i.e., it's the first processor), we provide the sample document */}
+ {JSON.stringify(
+ processorInput ? processorInput : documents[selectedDocumentIndex],
+ null,
+ 2
+ )}
+
+ >
+
+
+
+
+ {/* Data-out content */}
+
+ {i18nTexts.processorOutputLabel}
+
+
+
+
+ {getOutputContent()}
+
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_settings_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_settings_fields.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processor_settings_fields.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processor_settings_fields.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/append.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/append.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/append.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/append.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/bytes.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/bytes.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/bytes.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/bytes.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/circle.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/circle.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/circle.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/circle.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/common_processor_fields.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/common_processor_fields.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/common_processor_fields.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/common_processor_fields.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/field_name_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/field_name_field.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/field_name_field.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/field_name_field.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/ignore_missing_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/ignore_missing_field.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/ignore_missing_field.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/ignore_missing_field.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/index.ts
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/index.ts
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/index.ts
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/processor_type_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/processor_type_field.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/processor_type_field.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/processor_type_field.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/properties_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/properties_field.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/properties_field.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/properties_field.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/target_field.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/target_field.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/common_fields/target_field.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/common_fields/target_field.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/convert.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/convert.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/convert.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/convert.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/csv.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/csv.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/csv.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/csv.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/custom.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/custom.tsx
similarity index 96%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/custom.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/custom.tsx
index c2aab62cf8933..f49e77501f931 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/custom.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/custom.tsx
@@ -17,6 +17,7 @@ import {
const { emptyField, isJsonField } = fieldValidators;
import { XJsonEditor } from '../field_components';
+import { Fields } from '../processor_form.container';
import { EDITOR_PX_HEIGHT } from './shared';
const customConfig: FieldConfig = {
@@ -60,7 +61,7 @@ const customConfig: FieldConfig = {
};
interface Props {
- defaultOptions?: any;
+ defaultOptions?: Fields['fields'];
}
/**
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/date.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/date.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/date.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/date.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/date_index_name.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/date_index_name.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/date_index_name.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/date_index_name.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/dissect.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/dissect.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/dissect.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/dissect.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/dot_expander.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/dot_expander.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/dot_expander.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/dot_expander.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/drop.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/drop.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/drop.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/drop.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/enrich.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/enrich.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/enrich.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/enrich.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/fail.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/fail.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/fail.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/fail.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/foreach.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/foreach.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/foreach.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/foreach.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/geoip.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/geoip.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/geoip.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/geoip.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/grok.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/grok.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/grok.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/grok.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/gsub.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/gsub.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/gsub.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/gsub.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/html_strip.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/html_strip.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/html_strip.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/html_strip.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/index.ts
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/index.ts
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/index.ts
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/inference.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/inference.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/inference.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/inference.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/join.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/join.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/join.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/join.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/json.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/json.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/json.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/json.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/kv.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/kv.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/kv.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/kv.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/lowercase.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/lowercase.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/lowercase.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/lowercase.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/pipeline.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/pipeline.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/pipeline.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/pipeline.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/remove.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/remove.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/remove.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/remove.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/rename.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/rename.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/rename.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/rename.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/script.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/script.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/script.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/script.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/set.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/set.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/set.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/set.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/set_security_user.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/set_security_user.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/set_security_user.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/set_security_user.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/shared.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/shared.ts
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/shared.ts
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/shared.ts
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/sort.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/sort.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/sort.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/sort.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/split.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/split.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/split.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/split.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/trim.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/trim.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/trim.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/trim.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/uppercase.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/uppercase.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/uppercase.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/uppercase.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/url_decode.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/url_decode.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/url_decode.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/url_decode.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/user_agent.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/user_agent.tsx
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/manage_processor_form/processors/user_agent.tsx
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/processor_form/processors/user_agent.tsx
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/index.ts
index 1b4b975b5305e..3f258bf279e42 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/index.ts
@@ -9,3 +9,5 @@ export {
mapProcessorTypeToDescriptor,
ProcessorType,
} from './map_processor_type_to_form';
+
+export { ErrorIcon, ErrorIgnoredIcon, SkippedIcon } from './status_icons';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx
index 95a8d35c119a6..8d9260f3c822c 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/map_processor_type_to_form.tsx
@@ -46,7 +46,7 @@ import {
UrlDecode,
UserAgent,
FormFieldsComponent,
-} from '../manage_processor_form/processors';
+} from '../processor_form/processors';
interface FieldDescriptor {
FieldsComponent?: FormFieldsComponent;
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_icon.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_icon.tsx
new file mode 100644
index 0000000000000..58cb56d4f768d
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_icon.tsx
@@ -0,0 +1,18 @@
+/*
+ * 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 React, { FunctionComponent } from 'react';
+
+export const ErrorIcon: FunctionComponent = () => (
+
+
+
+);
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_ignored_icon.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_ignored_icon.tsx
new file mode 100644
index 0000000000000..74ceda7687f02
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/error_ignored_icon.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 React, { FunctionComponent } from 'react';
+
+export const ErrorIgnoredIcon: FunctionComponent = () => (
+
+
+
+
+);
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/index.ts
new file mode 100644
index 0000000000000..9fe0871e445eb
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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.
+ */
+
+export { ErrorIcon } from './error_icon';
+export { ErrorIgnoredIcon } from './error_ignored_icon';
+export { SkippedIcon } from './skipped_icon';
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/skipped_icon.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/skipped_icon.tsx
new file mode 100644
index 0000000000000..c540bd3790fb0
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/shared/status_icons/skipped_icon.tsx
@@ -0,0 +1,18 @@
+/*
+ * 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 React, { FunctionComponent } from 'react';
+
+export const SkippedIcon: FunctionComponent = () => (
+
+
+
+);
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/add_documents_button.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/add_documents_button.tsx
index e3ef9a9ee5390..26492454cbcf5 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/add_documents_button.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/add_documents_button.tsx
@@ -6,6 +6,7 @@
import { i18n } from '@kbn/i18n';
import React, { FunctionComponent } from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
+import { TestPipelineFlyoutTab } from './test_pipeline_flyout_tabs';
const i18nTexts = {
buttonLabel: i18n.translate('xpack.ingestPipelines.pipelineEditor.testPipeline.buttonLabel', {
@@ -14,16 +15,15 @@ const i18nTexts = {
};
interface Props {
- openTestPipelineFlyout: () => void;
+ openFlyout: (activeFlyoutTab: TestPipelineFlyoutTab) => void;
}
-export const AddDocumentsButton: FunctionComponent = ({ openTestPipelineFlyout }) => {
+export const AddDocumentsButton: FunctionComponent = ({ openFlyout }) => {
return (
openFlyout('documents')}
data-test-subj="addDocumentsButton"
- iconType="plusInCircleFilled"
>
{i18nTexts.buttonLabel}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.scss b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.scss
new file mode 100644
index 0000000000000..5deb48a2f01a7
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.scss
@@ -0,0 +1,3 @@
+.documentsDropdownPanel {
+ min-width: 200px;
+}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx
new file mode 100644
index 0000000000000..269a697a33c17
--- /dev/null
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/documents_dropdown.tsx
@@ -0,0 +1,138 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import React, { FunctionComponent, useState } from 'react';
+import {
+ EuiButton,
+ EuiPopover,
+ EuiButtonEmpty,
+ EuiPopoverTitle,
+ EuiSelectable,
+ EuiHorizontalRule,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+} from '@elastic/eui';
+
+import { Document } from '../../../types';
+
+import { TestPipelineFlyoutTab } from '../test_pipeline_flyout_tabs';
+
+import './documents_dropdown.scss';
+
+const i18nTexts = {
+ dropdownLabel: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsdropdown.dropdownLabel',
+ {
+ defaultMessage: 'Documents:',
+ }
+ ),
+ addDocumentsButtonLabel: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsDropdown.buttonLabel',
+ {
+ defaultMessage: 'Add documents',
+ }
+ ),
+ popoverTitle: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.testPipeline.documentsDropdown.popoverTitle',
+ {
+ defaultMessage: 'Test documents',
+ }
+ ),
+};
+
+interface Props {
+ documents: Document[];
+ selectedDocumentIndex: number;
+ updateSelectedDocument: (index: number) => void;
+ openFlyout: (activeFlyoutTab: TestPipelineFlyoutTab) => void;
+}
+
+export const DocumentsDropdown: FunctionComponent = ({
+ documents,
+ selectedDocumentIndex,
+ updateSelectedDocument,
+ openFlyout,
+}) => {
+ const [showPopover, setShowPopover] = useState(false);
+
+ const managePipelineButton = (
+ setShowPopover((previousBool) => !previousBool)}
+ iconType="arrowDown"
+ iconSide="right"
+ >
+ {i18n.translate('xpack.ingestPipelines.pipelineEditor.testPipeline.selectedDocumentLabel', {
+ defaultMessage: 'Document {selectedDocument}',
+ values: {
+ selectedDocument: selectedDocumentIndex + 1,
+ },
+ })}
+
+ );
+
+ return (
+ setShowPopover(false)}
+ button={managePipelineButton}
+ panelPaddingSize="none"
+ withTitle
+ repositionOnScroll
+ data-test-subj="documentsDropdown"
+ panelClassName="documentsDropdownPanel"
+ >
+ ({
+ key: index.toString(),
+ checked: selectedDocumentIndex === index ? 'on' : undefined,
+ label: i18n.translate('xpack.ingestPipelines.pipelineEditor.testPipeline.documentLabel', {
+ defaultMessage: 'Document {documentNumber}',
+ values: {
+ documentNumber: index + 1,
+ },
+ }),
+ }))}
+ onChange={(newOptions) => {
+ const selectedOption = newOptions.find((option) => option.checked === 'on');
+ if (selectedOption) {
+ updateSelectedDocument(Number(selectedOption.key!));
+ }
+
+ setShowPopover(false);
+ }}
+ >
+ {(list, search) => (
+
+ {i18nTexts.popoverTitle}
+ {list}
+
+ )}
+
+
+
+
+
+
+ {
+ openFlyout('documents');
+ setShowPopover(false);
+ }}
+ data-test-subj="addDocumentsButton"
+ >
+ {i18nTexts.addDocumentsButtonLabel}
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/index.ts
similarity index 100%
rename from x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/documents_dropdown/index.ts
rename to x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/documents_dropdown/index.ts
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 6fd1adad54f84..9018042229590 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
@@ -5,7 +5,8 @@
*/
import { i18n } from '@kbn/i18n';
import React, { FunctionComponent } from 'react';
-import { EuiButton, EuiToolTip } from '@elastic/eui';
+import { EuiButton } from '@elastic/eui';
+import { TestPipelineFlyoutTab } from './test_pipeline_flyout_tabs';
const i18nTexts = {
buttonLabel: i18n.translate(
@@ -14,46 +15,15 @@ const i18nTexts = {
defaultMessage: 'View output',
}
),
- disabledButtonTooltipLabel: i18n.translate(
- 'xpack.ingestPipelines.pipelineEditor.testPipeline.outputButtonTooltipLabel',
- {
- defaultMessage: 'Add documents to view the output',
- }
- ),
};
interface Props {
- isDisabled: boolean;
- openTestPipelineFlyout: () => void;
+ openFlyout: (activeFlyoutTab: TestPipelineFlyoutTab) => void;
}
-export const TestOutputButton: FunctionComponent = ({
- isDisabled,
- openTestPipelineFlyout,
-}) => {
- if (isDisabled) {
- return (
- {i18nTexts.disabledButtonTooltipLabel}
}>
-
- {i18nTexts.buttonLabel}
-
-
- );
- }
-
+export const TestOutputButton: FunctionComponent = ({ openFlyout }) => {
return (
-
+ openFlyout('output')} data-test-subj="viewOutputButton">
{i18nTexts.buttonLabel}
);
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_actions.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_actions.tsx
index eb9d9352e4b90..cec02db26729d 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_actions.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/components/test_pipeline/test_pipeline_actions.tsx
@@ -4,15 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { FunctionComponent, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
-import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { useTestPipelineContext, usePipelineProcessorsContext } from '../../context';
-
-import { DocumentsDropdown } from '../documents_dropdown';
+import { DocumentsDropdown } from './documents_dropdown';
import { TestPipelineFlyoutTab } from './test_pipeline_flyout_tabs';
import { AddDocumentsButton } from './add_documents_button';
import { TestOutputButton } from './test_output_button';
-import { TestPipelineFlyout } from './test_pipeline_flyout';
+import { TestPipelineFlyout } from './test_pipeline_flyout.container';
+
+const i18nTexts = {
+ testPipelineActionsLabel: i18n.translate(
+ 'xpack.ingestPipelines.pipelineEditor.testPipeline.testPipelineActionsLabel',
+ {
+ defaultMessage: 'Test pipeline:',
+ }
+ ),
+};
export const TestPipelineActions: FunctionComponent = () => {
const { testPipelineData, setCurrentTestPipelineData } = useTestPipelineContext();
@@ -40,35 +49,42 @@ export const TestPipelineActions: FunctionComponent = () => {
});
};
+ const openFlyout = (activeTab: TestPipelineFlyoutTab) => {
+ setOpenTestPipelineFlyout(true);
+ setActiveFlyoutTab(activeTab);
+ };
+
return (
<>
-
+
+
+
+
+ {i18nTexts.testPipelineActionsLabel}
+
+
+
+
{documents ? (
) : (
- {
- setOpenTestPipelineFlyout(true);
- setActiveFlyoutTab('documents');
- }}
- />
+
)}
-
- {
- setOpenTestPipelineFlyout(true);
- setActiveFlyoutTab('output');
- }}
- />
-
+
+ {testOutputPerProcessor && (
+
+
+
+ )}
+
{openTestPipelineFlyout && (
void;
+ processors: DeserializeResult;
+}
+
+export interface TestPipelineConfig {
+ documents: Document[];
+ verbose?: boolean;
+}
+
+export const TestPipelineFlyout: React.FunctionComponent = ({
+ onClose,
+ activeTab,
+ processors,
+}) => {
+ const { services } = useKibana();
+
+ const {
+ testPipelineData,
+ setCurrentTestPipelineData,
+ updateTestOutputPerProcessor,
+ } = useTestPipelineContext();
+
+ const {
+ config: { documents: cachedDocuments, verbose: cachedVerbose },
+ } = testPipelineData;
+
+ const { form } = useForm({
+ schema: documentsSchema,
+ defaultValue: {
+ documents: cachedDocuments || '',
+ },
+ });
+
+ const [selectedTab, setSelectedTab] = useState(activeTab);
+
+ const [isRunningTest, setIsRunningTest] = useState(false);
+ const [testingError, setTestingError] = useState(null);
+ const [testOutput, setTestOutput] = useState(undefined);
+
+ const handleTestPipeline = useCallback(
+ async (
+ { documents, verbose }: TestPipelineConfig,
+ updateProcessorOutput?: boolean
+ ): Promise<{ isSuccessful: boolean }> => {
+ const serializedProcessors = serialize({ pipeline: processors });
+
+ setIsRunningTest(true);
+ setTestingError(null);
+
+ const { error, data: currentTestOutput } = await services.api.simulatePipeline({
+ documents,
+ verbose,
+ pipeline: { ...serializedProcessors },
+ });
+
+ setIsRunningTest(false);
+
+ if (error) {
+ setTestingError(error);
+
+ // reset the per-processor output
+ // this is needed in the scenario where the pipeline has already executed,
+ // but you modified the sample documents and there was an error on re-execution
+ setCurrentTestPipelineData({
+ type: 'updateOutputPerProcessor',
+ payload: {
+ isExecutingPipeline: false,
+ testOutputPerProcessor: undefined,
+ },
+ });
+
+ return { isSuccessful: false };
+ }
+
+ setCurrentTestPipelineData({
+ type: 'updateConfig',
+ payload: {
+ config: {
+ documents,
+ verbose,
+ },
+ },
+ });
+
+ // We sometimes need to manually refresh the per-processor output
+ // e.g., when clicking the "Refresh output" button and there have been no state changes
+ if (updateProcessorOutput) {
+ updateTestOutputPerProcessor(documents, processors);
+ }
+
+ setTestOutput(currentTestOutput);
+
+ services.notifications.toasts.addSuccess(
+ i18n.translate('xpack.ingestPipelines.testPipelineFlyout.successNotificationText', {
+ defaultMessage: 'Pipeline executed',
+ }),
+ {
+ toastLifeTimeMs: 1000,
+ }
+ );
+
+ return { isSuccessful: true };
+ },
+ [
+ processors,
+ services.api,
+ services.notifications.toasts,
+ setCurrentTestPipelineData,
+ updateTestOutputPerProcessor,
+ ]
+ );
+
+ const validateAndTestPipeline = async () => {
+ const { isValid, data } = await form.submit();
+
+ if (!isValid) {
+ return;
+ }
+
+ const { documents } = data as { documents: Document[] };
+
+ const { isSuccessful } = await handleTestPipeline({
+ documents: documents!,
+ verbose: cachedVerbose,
+ });
+
+ if (isSuccessful) {
+ setSelectedTab('output');
+ }
+ };
+
+ useEffect(() => {
+ if (cachedDocuments && activeTab === 'output') {
+ handleTestPipeline({ documents: cachedDocuments, verbose: cachedVerbose }, true);
+ }
+ // We only want to know on initial mount if
+ // there are cached documents and we are on the output tab
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+
+ );
+};
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 b26c6f536366d..46271a6bce51c 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
@@ -4,9 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useState, useCallback, useEffect } from 'react';
+import React from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
-import { i18n } from '@kbn/i18n';
import {
EuiFlyout,
@@ -17,112 +16,46 @@ import {
EuiCallOut,
} from '@elastic/eui';
-import { useKibana } from '../../../../../shared_imports';
-import { useTestPipelineContext } from '../../context';
-import { serialize } from '../../serialize';
-import { DeserializeResult } from '../../deserialize';
+import { Form, FormHook } from '../../../../../shared_imports';
import { Document } from '../../types';
import { Tabs, TestPipelineFlyoutTab, OutputTab, DocumentsTab } from './test_pipeline_flyout_tabs';
export interface Props {
- activeTab: TestPipelineFlyoutTab;
onClose: () => void;
- processors: DeserializeResult;
+ handleTestPipeline: (
+ testPipelineConfig: TestPipelineConfig,
+ refreshOutputPerProcessor?: boolean
+ ) => Promise<{ isSuccessful: boolean }>;
+ isRunningTest: boolean;
+ cachedVerbose?: boolean;
+ cachedDocuments?: Document[];
+ testOutput?: any;
+ form: FormHook;
+ validateAndTestPipeline: () => Promise;
+ selectedTab: TestPipelineFlyoutTab;
+ setSelectedTab: (selectedTa: TestPipelineFlyoutTab) => void;
+ testingError: any;
}
-export interface HandleTestPipelineArgs {
+export interface TestPipelineConfig {
documents: Document[];
verbose?: boolean;
}
export const TestPipelineFlyout: React.FunctionComponent = ({
+ handleTestPipeline,
+ isRunningTest,
+ cachedVerbose,
+ cachedDocuments,
+ testOutput,
+ form,
+ validateAndTestPipeline,
+ selectedTab,
+ setSelectedTab,
+ testingError,
onClose,
- activeTab,
- processors,
}) => {
- const { services } = useKibana();
-
- const {
- testPipelineData,
- setCurrentTestPipelineData,
- updateTestOutputPerProcessor,
- } = useTestPipelineContext();
-
- const {
- config: { documents: cachedDocuments, verbose: cachedVerbose },
- } = testPipelineData;
-
- const [selectedTab, setSelectedTab] = useState(activeTab);
-
- const [shouldTestImmediately, setShouldTestImmediately] = useState(false);
- const [isRunningTest, setIsRunningTest] = useState(false);
- const [testingError, setTestingError] = useState(null);
- const [testOutput, setTestOutput] = useState(undefined);
-
- const handleTestPipeline = useCallback(
- async ({ documents, verbose }: HandleTestPipelineArgs) => {
- const serializedProcessors = serialize({ pipeline: processors });
-
- setIsRunningTest(true);
- setTestingError(null);
-
- const { error, data: currentTestOutput } = await services.api.simulatePipeline({
- documents,
- verbose,
- pipeline: { ...serializedProcessors },
- });
-
- setIsRunningTest(false);
-
- if (error) {
- setTestingError(error);
- return;
- }
-
- setCurrentTestPipelineData({
- type: 'updateConfig',
- payload: {
- config: {
- documents,
- verbose,
- },
- },
- });
-
- setTestOutput(currentTestOutput);
-
- services.notifications.toasts.addSuccess(
- i18n.translate('xpack.ingestPipelines.testPipelineFlyout.successNotificationText', {
- defaultMessage: 'Pipeline executed',
- }),
- {
- toastLifeTimeMs: 1000,
- }
- );
-
- setSelectedTab('output');
- },
- [services.api, processors, setCurrentTestPipelineData, services.notifications.toasts]
- );
-
- useEffect(() => {
- if (cachedDocuments) {
- setShouldTestImmediately(true);
- }
- // We only want to know on initial mount if there are cached documents
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- useEffect(() => {
- // If the user has already tested the pipeline once,
- // use the cached test config and automatically execute the pipeline
- if (shouldTestImmediately) {
- setShouldTestImmediately(false);
- handleTestPipeline({ documents: cachedDocuments!, verbose: cachedVerbose });
- }
- }, [handleTestPipeline, cachedDocuments, cachedVerbose, shouldTestImmediately]);
-
let tabContent;
if (selectedTab === 'output') {
@@ -138,13 +71,19 @@ export const TestPipelineFlyout: React.FunctionComponent = ({
} else {
// default to "Documents" tab
tabContent = (
-
+
);
}
@@ -163,9 +102,17 @@ export const TestPipelineFlyout: React.FunctionComponent = ({
{
+ if (nextTab === 'output') {
+ // When switching to the output tab,
+ // we automatically run the pipeline if documents are defined
+ validateAndTestPipeline();
+ } else {
+ form.reset({ defaultValue: { documents: cachedDocuments! } });
+ setSelectedTab(nextTab);
+ }
+ }}
selectedTab={selectedTab}
- getIsDisabled={(tabId) => !testOutput && tabId === 'output'}
/>
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 dd12cdab0c934..b2326644340a7 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
@@ -10,67 +10,23 @@ import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiText, EuiButton, EuiLink } from '@elastic/eui';
-import {
- getUseField,
- Field,
- JsonEditorField,
- Form,
- useForm,
- useKibana,
-} from '../../../../../../shared_imports';
-
-import { TestPipelineContext } from '../../../context';
-import { Document } from '../../../types';
-import { DeserializeResult } from '../../../deserialize';
-import { HandleTestPipelineArgs } from '../test_pipeline_flyout';
-import { documentsSchema } from './documents_schema';
+import { getUseField, Field, JsonEditorField, useKibana } from '../../../../../../shared_imports';
const UseField = getUseField({ component: Field });
interface Props {
- handleTestPipeline: (data: HandleTestPipelineArgs) => void;
- setPerProcessorOutput: (documents: Document[] | undefined, processors: DeserializeResult) => void;
+ validateAndTestPipeline: () => Promise;
isRunningTest: boolean;
- processors: DeserializeResult;
- testPipelineData: TestPipelineContext['testPipelineData'];
+ isSubmitButtonDisabled: boolean;
}
export const DocumentsTab: React.FunctionComponent = ({
- handleTestPipeline,
+ validateAndTestPipeline,
+ isSubmitButtonDisabled,
isRunningTest,
- setPerProcessorOutput,
- processors,
- testPipelineData,
}) => {
const { services } = useKibana();
- const {
- config: { documents: cachedDocuments, verbose: cachedVerbose },
- } = testPipelineData;
-
- const testPipeline = async () => {
- const { isValid, data } = await form.submit();
-
- if (!isValid) {
- return;
- }
-
- const { documents } = data as { documents: Document[] };
-
- await handleTestPipeline({ documents: documents!, verbose: cachedVerbose });
-
- // This is necessary to update the status and output of each processor
- // as verbose may not be enabled
- setPerProcessorOutput(documents, processors);
- };
-
- const { form } = useForm({
- schema: documentsSchema,
- defaultValue: {
- documents: cachedDocuments || '',
- },
- });
-
return (
@@ -100,53 +56,46 @@ export const DocumentsTab: React.FunctionComponent = ({
-
+
+ {isRunningTest ? (
+
+ ) : (
+
+ )}
+
);
};
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 926bab6da993c..db6a020e307a5 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
@@ -18,10 +18,13 @@ import {
} from '@elastic/eui';
import { Document } from '../../../types';
-import { HandleTestPipelineArgs } from '../test_pipeline_flyout';
+import { TestPipelineConfig } from '../test_pipeline_flyout.container';
interface Props {
- handleTestPipeline: (data: HandleTestPipelineArgs) => void;
+ handleTestPipeline: (
+ testPipelineConfig: TestPipelineConfig,
+ refreshOutputPerProcessor?: boolean
+ ) => Promise<{ isSuccessful: boolean }>;
isRunningTest: boolean;
cachedVerbose?: boolean;
cachedDocuments: Document[];
@@ -37,12 +40,6 @@ export const OutputTab: React.FunctionComponent = ({
}) => {
const [isVerboseEnabled, setIsVerboseEnabled] = useState(Boolean(cachedVerbose));
- const onEnableVerbose = (isVerbose: boolean) => {
- setIsVerboseEnabled(isVerbose);
-
- handleTestPipeline({ documents: cachedDocuments!, verbose: isVerbose });
- };
-
let content: React.ReactNode | undefined;
if (isRunningTest) {
@@ -78,15 +75,23 @@ export const OutputTab: React.FunctionComponent = ({
/>
}
checked={isVerboseEnabled}
- onChange={(e) => onEnableVerbose(e.target.checked)}
data-test-subj="verboseOutputToggle"
+ onChange={async (e) => {
+ const isVerbose = e.target.checked;
+ setIsVerboseEnabled(isVerbose);
+
+ await handleTestPipeline({ documents: cachedDocuments!, verbose: isVerbose });
+ }}
/>
- handleTestPipeline({ documents: cachedDocuments!, verbose: isVerboseEnabled })
+ onClick={async () =>
+ await handleTestPipeline(
+ { documents: cachedDocuments!, verbose: isVerboseEnabled },
+ true
+ )
}
iconType="refresh"
data-test-subj="refreshOutputButton"
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 abfb86c2afda1..b13fb2df90984 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
@@ -13,14 +13,9 @@ export type TestPipelineFlyoutTab = 'documents' | 'output';
interface Props {
onTabChange: (tab: TestPipelineFlyoutTab) => void;
selectedTab: TestPipelineFlyoutTab;
- getIsDisabled: (tab: TestPipelineFlyoutTab) => boolean;
}
-export const Tabs: React.FunctionComponent = ({
- onTabChange,
- selectedTab,
- getIsDisabled,
-}) => {
+export const Tabs: React.FunctionComponent = ({ onTabChange, selectedTab }) => {
const tabs: Array<{
id: TestPipelineFlyoutTab;
name: React.ReactNode;
@@ -29,8 +24,8 @@ export const Tabs: React.FunctionComponent = ({
id: 'documents',
name: (
),
},
@@ -49,7 +44,6 @@ export const Tabs: React.FunctionComponent = ({
onClick={() => onTabChange(tab.id)}
isSelected={tab.id === selectedTab}
key={tab.id}
- disabled={getIsDisabled(tab.id)}
data-test-subj={tab.id.toLowerCase() + 'Tab'}
>
{tab.name}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx
index 8c59d484acd08..6595437c01810 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/context/processors_context.tsx
@@ -38,7 +38,7 @@ import { OnActionHandler } from '../components/processors_tree';
import {
ProcessorRemoveModal,
PipelineProcessorsItemTooltip,
- ManageProcessorForm,
+ ProcessorForm,
OnSubmitHandler,
} from '../components';
@@ -159,12 +159,12 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({
selector: mode.arg.selector,
},
});
+
break;
default:
}
- setMode({ id: 'idle' });
},
- [processorsDispatch, mode, setMode]
+ [processorsDispatch, mode]
);
const onCloseSettingsForm = useCallback(() => {
@@ -208,8 +208,8 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({
};
}, [mode, setMode, processorsState, processorsDispatch]);
- // Update the test output whenever the processorsState changes (e.g., on move, update, delete)
- // Note: updateTestOutputPerProcessor() will only simulate if the user has added sample documents
+ // Make a request to the simulate API and update the processor output
+ // whenever the documents or processorsState changes (e.g., on move, update, delete)
useEffect(() => {
updateTestOutputPerProcessor(documents, processorsState);
}, [documents, processorsState, updateTestOutputPerProcessor]);
@@ -233,7 +233,7 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({
)}
{mode.id === 'managingProcessor' || mode.id === 'creatingProcessor' ? (
- {
+ const previousProcessorIndex = processorIndex - count;
+
+ if (previousProcessorIndex >= 0) {
+ const processorResult = document.processor_results[previousProcessorIndex];
+
+ if (!processorResult.doc) {
+ const newCount = count + 1;
+ return getProcessorInput(processorIndex, document, newCount);
+ }
+
+ return processorResult.doc;
+ }
+
+ return undefined;
+};
+
/**
* This function takes the verbose response of the simulate API
* and maps the results to each processor in the pipeline by the "tag" field
@@ -81,11 +114,9 @@ export const deserializeVerboseTestOutput = (
const result = { ...currentResult };
const resultId = result.tag;
+ // We skip index 0, as the first processor will not have a previous result
if (index !== 0) {
- // Add the result from the previous processor so that the user
- // can easily compare current output to the previous output
- // This may be a result from an on_failure processor
- result.prevProcessorResult = doc.processor_results[index - 1];
+ result.processorInput = getProcessorInput(index, doc);
}
// The tag is added programatically as a way to map
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts
index 71b2e2fa8f7f1..c462b19c79327 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_processors_editor/index.ts
@@ -14,9 +14,4 @@ export { OnUpdateHandlerArg, OnUpdateHandler } from './types';
export { SerializeResult } from './serialize';
-export {
- LoadFromJsonButton,
- OnDoneLoadJsonHandler,
- TestPipelineActions,
- DocumentsDropdown,
-} from './components';
+export { LoadFromJsonButton, OnDoneLoadJsonHandler, TestPipelineActions } from './components';
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 5229f5eb0bb21..42201b3102c28 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
@@ -94,7 +94,7 @@ export interface ProcessorResult {
tag: string;
ignored_error?: any;
error?: any;
- prevProcessorResult?: ProcessorResult;
+ processorInput?: Document;
[key: string]: any;
}
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
index f1dc3fa306d15..e6503cb793a8e 100644
--- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
+++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
@@ -338,7 +338,6 @@ export function SuggestionPanel({
if (lastSelectedSuggestion === index) {
rollbackToCurrentVisualization();
} else {
- trackSuggestionEvent(`position_${index}_of_${suggestions.length}`);
setLastSelectedSuggestion(index);
switchToSuggestion(dispatch, suggestion);
}
diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx
index 98e9389a85819..153757ac37da1 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_editor.tsx
@@ -213,9 +213,6 @@ export function DimensionEditor(props: DimensionEditorProps) {
previousColumn: selectedColumn,
});
- trackUiEvent(
- `indexpattern_dimension_operation_from_${selectedColumn.operationType}_to_${operationType}`
- );
setState(
changeColumn({
state,
diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts
index 3589ec41e4db3..d72d04d2a1843 100644
--- a/x-pack/plugins/maps/common/constants.ts
+++ b/x-pack/plugins/maps/common/constants.ts
@@ -263,3 +263,7 @@ export enum MB_LOOKUP_FUNCTION {
GET = 'get',
FEATURE_STATE = 'feature-state',
}
+
+export type RawValue = string | number | boolean | undefined | null;
+
+export type FieldFormatter = (value: RawValue) => string | number;
diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts
index 4a050cc3d7d19..7e7a7bd8f049d 100644
--- a/x-pack/plugins/maps/public/classes/sources/source.ts
+++ b/x-pack/plugins/maps/public/classes/sources/source.ts
@@ -12,7 +12,7 @@ import { Adapters } from 'src/plugins/inspector/public';
import { copyPersistentState } from '../../reducers/util';
import { IField } from '../fields/field';
-import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants';
+import { FieldFormatter, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants';
import { AbstractSourceDescriptor } from '../../../common/descriptor_types';
import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view';
@@ -37,8 +37,6 @@ export type PreIndexedShape = {
path: string;
};
-export type FieldFormatter = (value: string | number | null | undefined | boolean) => string;
-
export interface ISource {
destroy(): void;
getDisplayName(): Promise;
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx
index de8f3b5c09175..c9188a0a19b0d 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx
@@ -15,7 +15,7 @@ import { shallow } from 'enzyme';
import { Feature, Point } from 'geojson';
import { DynamicColorProperty } from './dynamic_color_property';
-import { COLOR_MAP_TYPE, VECTOR_STYLES } from '../../../../../common/constants';
+import { COLOR_MAP_TYPE, RawValue, VECTOR_STYLES } from '../../../../../common/constants';
import { mockField, MockLayer, MockStyle } from './__tests__/test_util';
import { ColorDynamicOptions } from '../../../../../common/descriptor_types';
import { IVectorLayer } from '../../../layers/vector_layer/vector_layer';
@@ -28,7 +28,7 @@ const makeProperty = (options: ColorDynamicOptions, style?: MockStyle, field?: I
field ? field : mockField,
(new MockLayer(style ? style : new MockStyle()) as unknown) as IVectorLayer,
() => {
- return (value: string | number | undefined) => value + '_format';
+ return (value: RawValue) => value + '_format';
}
);
};
@@ -273,7 +273,7 @@ describe('supportsFieldMeta', () => {
null,
(new MockLayer(new MockStyle()) as unknown) as IVectorLayer,
() => {
- return (value: string | number | undefined) => value + '_format';
+ return (value: RawValue) => value + '_format';
}
);
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx
index 06987ab8bcc48..2f9e4709c1c0b 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx
@@ -13,7 +13,7 @@ jest.mock('../components/vector_style_editor', () => ({
}));
import React from 'react';
-import { VECTOR_STYLES } from '../../../../../common/constants';
+import { RawValue, VECTOR_STYLES } from '../../../../../common/constants';
// @ts-ignore
import { DynamicIconProperty } from './dynamic_icon_property';
import { mockField, MockLayer } from './__tests__/test_util';
@@ -33,7 +33,7 @@ const makeProperty = (options: Partial, field: IField = mock
field,
mockVectorLayer,
() => {
- return (value: string | number | undefined) => value + '_format';
+ return (value: RawValue) => value + '_format';
}
);
};
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts
index dd976027a50f2..192fa1f4db6e0 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts
@@ -5,25 +5,29 @@
*/
import { Map as MbMap } from 'mapbox-gl';
-import { DynamicStyleProperty } from './dynamic_style_property';
-import { getComputedFieldName } from '../style_util';
-import { VECTOR_STYLES } from '../../../../../common/constants';
+import { DynamicStyleProperty, getNumericalMbFeatureStateValue } from './dynamic_style_property';
import { OrientationDynamicOptions } from '../../../../../common/descriptor_types';
+import { RawValue } from '../../../../../common/constants';
export class DynamicOrientationProperty extends DynamicStyleProperty {
syncIconRotationWithMb(symbolLayerId: string, mbMap: MbMap) {
if (this._field && this._field.isValid()) {
- const targetName = this._field.supportsAutoDomain()
- ? getComputedFieldName(VECTOR_STYLES.ICON_ORIENTATION, this.getFieldName())
- : this._field.getName();
- // Using property state instead of feature-state because layout properties do not support feature-state
- mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', ['coalesce', ['get', targetName], 0]);
+ const targetName = this.getMbPropertyName();
+ mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', [
+ 'coalesce',
+ [this.getMbLookupFunction(), targetName],
+ 0,
+ ]);
} else {
mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', 0);
}
}
- supportsMbFeatureState() {
+ supportsMbFeatureState(): boolean {
return false;
}
+
+ getMbPropertyValue(rawValue: RawValue): RawValue {
+ return getNumericalMbFeatureStateValue(rawValue);
+ }
}
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx
index c5298067f6cbe..b4244cf7829c4 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx
@@ -15,7 +15,7 @@ import { shallow } from 'enzyme';
// @ts-ignore
import { DynamicSizeProperty } from './dynamic_size_property';
-import { VECTOR_STYLES } from '../../../../../common/constants';
+import { RawValue, VECTOR_STYLES } from '../../../../../common/constants';
import { IField } from '../../../fields/field';
import { Map as MbMap } from 'mapbox-gl';
import { SizeDynamicOptions } from '../../../../../common/descriptor_types';
@@ -48,7 +48,7 @@ const makeProperty = (
field,
(new MockLayer(mockStyle) as unknown) as IVectorLayer,
() => {
- return (value: string | number | undefined) => value + '_format';
+ return (value: RawValue) => value + '_format';
},
false
);
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx
index 35c830f3cb5e3..4e75a61539ad9 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx
@@ -7,7 +7,7 @@
import _ from 'lodash';
import React from 'react';
import { Map as MbMap } from 'mapbox-gl';
-import { DynamicStyleProperty, FieldFormatter } from './dynamic_style_property';
+import { DynamicStyleProperty } from './dynamic_style_property';
import { OrdinalLegend } from '../components/legend/ordinal_legend';
import { makeMbClampedNumberExpression } from '../style_util';
import {
@@ -16,7 +16,7 @@ import {
SMALL_MAKI_ICON_SIZE,
// @ts-expect-error
} from '../symbol_utils';
-import { MB_LOOKUP_FUNCTION, VECTOR_STYLES } from '../../../../../common/constants';
+import { FieldFormatter, MB_LOOKUP_FUNCTION, VECTOR_STYLES } from '../../../../../common/constants';
import { SizeDynamicOptions } from '../../../../../common/descriptor_types';
import { IField } from '../../../fields/field';
import { IVectorLayer } from '../../../layers/vector_layer/vector_layer';
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx
index f6ab052497723..2bc819daeea90 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx
@@ -15,6 +15,8 @@ import {
SOURCE_META_DATA_REQUEST_ID,
STYLE_TYPE,
VECTOR_STYLES,
+ RawValue,
+ FieldFormatter,
} from '../../../../../common/constants';
import { OrdinalFieldMetaPopover } from '../components/field_meta/ordinal_field_meta_popover';
import { CategoricalFieldMetaPopover } from '../components/field_meta/categorical_field_meta_popover';
@@ -28,6 +30,7 @@ import { IField } from '../../../fields/field';
import { IVectorLayer } from '../../../layers/vector_layer/vector_layer';
import { IJoin } from '../../../joins/join';
import { IVectorStyle } from '../vector_style';
+import { getComputedFieldName } from '../style_util';
export interface IDynamicStyleProperty extends IStyleProperty {
getFieldMetaOptions(): FieldMetaOptions;
@@ -46,9 +49,16 @@ export interface IDynamicStyleProperty extends IStyleProperty {
pluckOrdinalStyleMetaFromFeatures(features: Feature[]): RangeFieldMeta | null;
pluckCategoricalStyleMetaFromFeatures(features: Feature[]): CategoryFieldMeta | null;
getValueSuggestions(query: string): Promise;
-}
-export type FieldFormatter = (value: string | number | undefined) => string | number;
+ // Returns the name that should be used for accessing the data from the mb-style rule
+ // Depending on
+ // - whether the field is used for labeling, icon-orientation, or other properties (color, size, ...), `feature-state` and or `get` is used
+ // - whether the field was run through a field-formatter, a new dynamic field is created with the formatted-value
+ // The combination of both will inform what field-name (e.g. the "raw" field name from the properties, the "computed field-name" for an on-the-fly created property (e.g. for feature-state or field-formatting).
+ // todo: There is an existing limitation to .mvt backed sources, where the field-formatters are not applied. Here, the raw-data needs to be accessed.
+ getMbPropertyName(): string;
+ getMbPropertyValue(value: RawValue): RawValue;
+}
export class DynamicStyleProperty
extends AbstractStyleProperty
@@ -313,7 +323,7 @@ export class DynamicStyleProperty
};
}
- formatField(value: string | number | undefined): string | number {
+ formatField(value: RawValue): string | number {
if (this.getField()) {
const fieldName = this.getFieldName();
const fieldFormatter = this._getFieldFormatter(fieldName);
@@ -345,4 +355,43 @@ export class DynamicStyleProperty
/>
);
}
+
+ getMbPropertyName() {
+ if (!this._field) {
+ return '';
+ }
+
+ let targetName;
+ if (this.supportsMbFeatureState()) {
+ // Base case for any properties that can support feature-state (e.g. color, size, ...)
+ // They just re-use the original property-name
+ targetName = this._field.getName();
+ } else {
+ if (this._field.canReadFromGeoJson() && this._field.supportsAutoDomain()) {
+ // Geojson-sources can support rewrite
+ // e.g. field-formatters will create duplicate field
+ targetName = getComputedFieldName(this.getStyleName(), this._field.getName());
+ } else {
+ // Non-geojson sources (e.g. 3rd party mvt or ES-source as mvt)
+ targetName = this._field.getName();
+ }
+ }
+ return targetName;
+ }
+
+ getMbPropertyValue(rawValue: RawValue): RawValue {
+ // Maps only uses feature-state for numerical values.
+ // `supportsMbFeatureState` will only return true when the mb-style rule does a feature-state lookup on a numerical value
+ // Calling `isOrdinal` would be equivalent.
+ return this.supportsMbFeatureState() ? getNumericalMbFeatureStateValue(rawValue) : rawValue;
+ }
+}
+
+export function getNumericalMbFeatureStateValue(value: RawValue) {
+ if (typeof value !== 'string') {
+ return value;
+ }
+
+ const valueAsFloat = parseFloat(value);
+ return isNaN(valueAsFloat) ? null : valueAsFloat;
}
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts
index d55a6e1cfb444..ec79d71eb7587 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts
+++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts
@@ -6,18 +6,18 @@
import { Map as MbMap } from 'mapbox-gl';
import { DynamicStyleProperty } from './dynamic_style_property';
-import { getComputedFieldName } from '../style_util';
import { LabelDynamicOptions } from '../../../../../common/descriptor_types';
+import { RawValue } from '../../../../../common/constants';
export class DynamicTextProperty extends DynamicStyleProperty {
syncTextFieldWithMb(mbLayerId: string, mbMap: MbMap) {
if (this._field && this._field.isValid()) {
- // Fields that support auto-domain are normalized with a field-formatter and stored into a computed-field
- // Otherwise, the raw value is just carried over and no computed field is created.
- const targetName = this._field.supportsAutoDomain()
- ? getComputedFieldName(this._styleName, this.getFieldName())
- : this._field.getName();
- mbMap.setLayoutProperty(mbLayerId, 'text-field', ['coalesce', ['get', targetName], '']);
+ const targetName = this.getMbPropertyName();
+ mbMap.setLayoutProperty(mbLayerId, 'text-field', [
+ 'coalesce',
+ [this.getMbLookupFunction(), targetName],
+ '',
+ ]);
} else {
mbMap.setLayoutProperty(mbLayerId, 'text-field', null);
}
@@ -34,4 +34,8 @@ export class DynamicTextProperty extends DynamicStyleProperty {
isDynamic(): boolean;
isComplete(): boolean;
- formatField(value: string | number | undefined): string | number;
+ formatField(value: RawValue): string | number;
getStyleName(): VECTOR_STYLES;
getOptions(): T;
renderLegendDetailRow(legendProps: LegendProps): ReactElement | null;
@@ -53,9 +53,14 @@ export class AbstractStyleProperty implements IStyleProperty {
return true;
}
- formatField(value: string | number | undefined): string | number {
- // eslint-disable-next-line eqeqeq
- return value == undefined ? '' : value;
+ formatField(value: RawValue): string | number {
+ if (typeof value === 'undefined' || value === null) {
+ return '';
+ } else if (typeof value === 'boolean') {
+ return value.toString();
+ } else {
+ return value;
+ }
}
getStyleName(): VECTOR_STYLES {
diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx
index 956522524a2eb..1244c53afe9a6 100644
--- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx
+++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx
@@ -29,7 +29,7 @@ import {
import { StyleMeta } from './style_meta';
import { VectorIcon } from './components/legend/vector_icon';
import { VectorStyleLegend } from './components/legend/vector_style_legend';
-import { getComputedFieldName, isOnlySingleFeatureType } from './style_util';
+import { isOnlySingleFeatureType } from './style_util';
import { StaticStyleProperty } from './properties/static_style_property';
import { DynamicStyleProperty } from './properties/dynamic_style_property';
import { DynamicSizeProperty } from './properties/dynamic_size_property';
@@ -82,11 +82,6 @@ const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT];
const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING];
const POLYGONS = [GEO_JSON_TYPE.POLYGON, GEO_JSON_TYPE.MULTI_POLYGON];
-function getNumericalMbFeatureStateValue(value: string) {
- const valueAsFloat = parseFloat(value);
- return isNaN(valueAsFloat) ? null : valueAsFloat;
-}
-
export interface IVectorStyle extends IStyle {
getAllStyleProperties(): Array>;
getDynamicPropertiesArray(): Array>;
@@ -618,21 +613,17 @@ export class VectorStyle implements IVectorStyle {
for (let j = 0; j < dynamicStyleProps.length; j++) {
const dynamicStyleProp = dynamicStyleProps[j];
- const name = dynamicStyleProp.getFieldName();
- const computedName = getComputedFieldName(dynamicStyleProp.getStyleName(), name);
- const rawValue = feature.properties ? feature.properties[name] : undefined;
+ const targetMbName = dynamicStyleProp.getMbPropertyName();
+ const rawValue = feature.properties
+ ? feature.properties[dynamicStyleProp.getFieldName()]
+ : undefined;
+ const targetMbValue = dynamicStyleProp.getMbPropertyValue(rawValue);
if (dynamicStyleProp.supportsMbFeatureState()) {
- tmpFeatureState[name] = getNumericalMbFeatureStateValue(rawValue); // the same value will be potentially overridden multiple times, if the name remains identical
+ tmpFeatureState[targetMbName] = targetMbValue; // the same value will be potentially overridden multiple times, if the name remains identical
} else {
- // in practice, a new system property will only be created for:
- // - label text: this requires the value to be formatted first.
- // - icon orientation: this is a lay-out property which do not support feature-state (but we're still coercing to a number)
-
- const formattedValue = dynamicStyleProp.isOrdinal()
- ? getNumericalMbFeatureStateValue(rawValue)
- : dynamicStyleProp.formatField(rawValue);
-
- if (feature.properties) feature.properties[computedName] = formattedValue;
+ if (feature.properties) {
+ feature.properties[targetMbName] = targetMbValue;
+ }
}
}
tmpFeatureIdentifier.source = mbSourceId;
diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts
index d9d9fde8fc8cc..17ff1dad79960 100644
--- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { newRule } from '../objects/rule';
+import { newRule, existingRule } from '../objects/rule';
import {
CUSTOM_RULES_BTN,
@@ -16,26 +16,16 @@ import {
SHOWING_RULES_TEXT,
} from '../screens/alerts_detection_rules';
import {
- ABOUT_FALSE_POSITIVES,
ABOUT_INVESTIGATION_NOTES,
- ABOUT_MITRE,
- ABOUT_RISK,
ABOUT_RULE_DESCRIPTION,
- ABOUT_SEVERITY,
- ABOUT_STEP,
- ABOUT_TAGS,
- ABOUT_URLS,
- DEFINITION_CUSTOM_QUERY,
- DEFINITION_INDEX_PATTERNS,
- DEFINITION_TIMELINE,
- DEFINITION_STEP,
INVESTIGATION_NOTES_MARKDOWN,
INVESTIGATION_NOTES_TOGGLE,
RULE_ABOUT_DETAILS_HEADER_TOGGLE,
RULE_NAME_HEADER,
- SCHEDULE_LOOPBACK,
- SCHEDULE_RUNS,
- SCHEDULE_STEP,
+ getDescriptionForTitle,
+ ABOUT_DETAILS,
+ DEFINITION_DETAILS,
+ SCHEDULE_DETAILS,
} from '../screens/rule_details';
import {
@@ -53,18 +43,38 @@ import {
selectNumberOfRules,
waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded,
waitForRulesToBeLoaded,
+ editFirstRule,
} from '../tasks/alerts_detection_rules';
import {
createAndActivateRule,
fillAboutRuleAndContinue,
fillDefineCustomRuleWithImportedQueryAndContinue,
- expectDefineFormToRepopulateAndContinue,
- expectAboutFormToRepopulateAndContinue,
+ goToAboutStepTab,
+ goToScheduleStepTab,
+ goToActionsStepTab,
+ fillAboutRule,
} from '../tasks/create_new_rule';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
import { DETECTIONS_URL } from '../urls/navigation';
+import {
+ ACTIONS_THROTTLE_INPUT,
+ CUSTOM_QUERY_INPUT,
+ DEFINE_INDEX_INPUT,
+ RULE_NAME_INPUT,
+ RULE_DESCRIPTION_INPUT,
+ TAGS_FIELD,
+ SEVERITY_DROPDOWN,
+ RISK_INPUT,
+ SCHEDULE_INTERVAL_AMOUNT_INPUT,
+ SCHEDULE_INTERVAL_UNITS_INPUT,
+ DEFINE_EDIT_BUTTON,
+ DEFINE_CONTINUE_BUTTON,
+ ABOUT_EDIT_BUTTON,
+ ABOUT_CONTINUE_BTN,
+} from '../screens/create_new_rule';
+import { saveEditedRule } from '../tasks/edit_rule';
describe('Detection rules, custom', () => {
before(() => {
@@ -84,8 +94,19 @@ describe('Detection rules, custom', () => {
goToCreateNewRule();
fillDefineCustomRuleWithImportedQueryAndContinue(newRule);
fillAboutRuleAndContinue(newRule);
- expectDefineFormToRepopulateAndContinue(newRule);
- expectAboutFormToRepopulateAndContinue(newRule);
+
+ // expect define step to repopulate
+ cy.get(DEFINE_EDIT_BUTTON).click();
+ cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', newRule.customQuery);
+ cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
+ cy.get(DEFINE_CONTINUE_BUTTON).should('not.exist');
+
+ // expect about step to populate
+ cy.get(ABOUT_EDIT_BUTTON).click();
+ cy.get(RULE_NAME_INPUT).invoke('val').should('eq', newRule.name);
+ cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true });
+ cy.get(ABOUT_CONTINUE_BTN).should('not.exist');
+
createAndActivateRule();
cy.get(CUSTOM_RULES_BTN).invoke('text').should('eql', 'Custom rules (1)');
@@ -142,32 +163,35 @@ describe('Detection rules, custom', () => {
cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${newRule.name} Beta`);
cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', newRule.description);
- cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', newRule.severity);
- cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', newRule.riskScore);
- cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls);
- cy.get(ABOUT_STEP)
- .eq(ABOUT_FALSE_POSITIVES)
- .invoke('text')
- .should('eql', expectedFalsePositives);
- cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre);
- cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags);
+ cy.get(ABOUT_DETAILS).within(() => {
+ getDescriptionForTitle('Severity').invoke('text').should('eql', newRule.severity);
+ getDescriptionForTitle('Risk score').invoke('text').should('eql', newRule.riskScore);
+ getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls);
+ getDescriptionForTitle('False positive examples')
+ .invoke('text')
+ .should('eql', expectedFalsePositives);
+ getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre);
+ getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags);
+ });
cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN);
- cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => {
- cy.wrap(patterns).each((pattern, index) => {
- cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]);
- });
+ cy.get(DEFINITION_DETAILS).within(() => {
+ getDescriptionForTitle('Index patterns')
+ .invoke('text')
+ .should('eql', expectedIndexPatterns.join(''));
+ getDescriptionForTitle('Custom query')
+ .invoke('text')
+ .should('eql', `${newRule.customQuery} `);
+ getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Query');
+ getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None');
+ });
+
+ cy.get(SCHEDULE_DETAILS).within(() => {
+ getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m');
+ getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m');
});
- cy.get(DEFINITION_STEP)
- .eq(DEFINITION_CUSTOM_QUERY)
- .invoke('text')
- .should('eql', `${newRule.customQuery} `);
- cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None');
-
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m');
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m');
});
});
@@ -233,4 +257,94 @@ describe('Deletes custom rules', () => {
.should('eql', `Custom rules (${expectedNumberOfRulesAfterDeletion})`);
});
});
+
+ it('Allows a rule to be edited', () => {
+ editFirstRule();
+
+ // expect define step to populate
+ cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', existingRule.customQuery);
+ if (existingRule.index && existingRule.index.length > 0) {
+ cy.get(DEFINE_INDEX_INPUT).invoke('text').should('eq', existingRule.index.join(''));
+ }
+
+ goToAboutStepTab();
+
+ // expect about step to populate
+ cy.get(RULE_NAME_INPUT).invoke('val').should('eql', existingRule.name);
+ cy.get(RULE_DESCRIPTION_INPUT).invoke('text').should('eql', existingRule.description);
+ cy.get(TAGS_FIELD).invoke('text').should('eql', existingRule.tags.join(''));
+
+ cy.get(SEVERITY_DROPDOWN).invoke('text').should('eql', existingRule.severity);
+ cy.get(RISK_INPUT).invoke('val').should('eql', existingRule.riskScore);
+
+ goToScheduleStepTab();
+
+ // expect schedule step to populate
+ const intervalParts = existingRule.interval && existingRule.interval.match(/[0-9]+|[a-zA-Z]+/g);
+ if (intervalParts) {
+ const [amount, unit] = intervalParts;
+ cy.get(SCHEDULE_INTERVAL_AMOUNT_INPUT).invoke('val').should('eql', amount);
+ cy.get(SCHEDULE_INTERVAL_UNITS_INPUT).invoke('val').should('eql', unit);
+ } else {
+ throw new Error('Cannot assert scheduling info on a rule without an interval');
+ }
+
+ goToActionsStepTab();
+
+ cy.get(ACTIONS_THROTTLE_INPUT).invoke('val').should('eql', 'no_actions');
+
+ goToAboutStepTab();
+
+ const editedRule = {
+ ...existingRule,
+ severity: 'Medium',
+ description: 'Edited Rule description',
+ };
+
+ fillAboutRule(editedRule);
+ saveEditedRule();
+
+ const expectedTags = editedRule.tags.join('');
+ const expectedIndexPatterns =
+ editedRule.index && editedRule.index.length
+ ? editedRule.index
+ : [
+ 'apm-*-transaction*',
+ 'auditbeat-*',
+ 'endgame-*',
+ 'filebeat-*',
+ 'logs-*',
+ 'packetbeat-*',
+ 'winlogbeat-*',
+ ];
+
+ cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${editedRule.name} Beta`);
+
+ cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', editedRule.description);
+ cy.get(ABOUT_DETAILS).within(() => {
+ getDescriptionForTitle('Severity').invoke('text').should('eql', editedRule.severity);
+ getDescriptionForTitle('Risk score').invoke('text').should('eql', editedRule.riskScore);
+ getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags);
+ });
+
+ cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
+ cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', editedRule.note);
+
+ cy.get(DEFINITION_DETAILS).within(() => {
+ getDescriptionForTitle('Index patterns')
+ .invoke('text')
+ .should('eql', expectedIndexPatterns.join(''));
+ getDescriptionForTitle('Custom query')
+ .invoke('text')
+ .should('eql', `${editedRule.customQuery} `);
+ getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Query');
+ getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None');
+ });
+
+ if (editedRule.interval) {
+ cy.get(SCHEDULE_DETAILS).within(() => {
+ getDescriptionForTitle('Runs every').invoke('text').should('eql', editedRule.interval);
+ });
+ }
+ });
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts
index c65cd8406099a..76871929fe050 100644
--- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_eql.spec.ts
@@ -15,26 +15,16 @@ import {
SEVERITY,
} from '../screens/alerts_detection_rules';
import {
- ABOUT_FALSE_POSITIVES,
+ ABOUT_DETAILS,
ABOUT_INVESTIGATION_NOTES,
- ABOUT_MITRE,
- ABOUT_RISK,
ABOUT_RULE_DESCRIPTION,
- ABOUT_SEVERITY,
- ABOUT_STEP,
- ABOUT_TAGS,
- ABOUT_URLS,
- DEFINITION_CUSTOM_QUERY,
- DEFINITION_INDEX_PATTERNS,
- DEFINITION_TIMELINE,
- DEFINITION_STEP,
+ DEFINITION_DETAILS,
+ getDescriptionForTitle,
INVESTIGATION_NOTES_MARKDOWN,
INVESTIGATION_NOTES_TOGGLE,
RULE_ABOUT_DETAILS_HEADER_TOGGLE,
RULE_NAME_HEADER,
- SCHEDULE_LOOPBACK,
- SCHEDULE_RUNS,
- SCHEDULE_STEP,
+ SCHEDULE_DETAILS,
} from '../screens/rule_details';
import {
@@ -136,32 +126,34 @@ describe('Detection rules, EQL', () => {
cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${eqlRule.name} Beta`);
cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', eqlRule.description);
- cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', eqlRule.severity);
- cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', eqlRule.riskScore);
- cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls);
- cy.get(ABOUT_STEP)
- .eq(ABOUT_FALSE_POSITIVES)
- .invoke('text')
- .should('eql', expectedFalsePositives);
- cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre);
- cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags);
+ cy.get(ABOUT_DETAILS).within(() => {
+ getDescriptionForTitle('Severity').invoke('text').should('eql', eqlRule.severity);
+ getDescriptionForTitle('Risk score').invoke('text').should('eql', eqlRule.riskScore);
+ getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls);
+ getDescriptionForTitle('False positive examples')
+ .invoke('text')
+ .should('eql', expectedFalsePositives);
+ getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre);
+ getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags);
+ });
cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN);
- cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => {
- cy.wrap(patterns).each((pattern, index) => {
- cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]);
- });
+ cy.get(DEFINITION_DETAILS).within(() => {
+ getDescriptionForTitle('Index patterns')
+ .invoke('text')
+ .should('eql', expectedIndexPatterns.join(''));
+ getDescriptionForTitle('Custom query')
+ .invoke('text')
+ .should('eql', `${eqlRule.customQuery} `);
+ getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Event Correlation');
+ getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None');
+ });
+
+ cy.get(SCHEDULE_DETAILS).within(() => {
+ getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m');
+ getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m');
});
- cy.get(DEFINITION_STEP)
- .eq(DEFINITION_CUSTOM_QUERY)
- .invoke('text')
- .should('eql', `${eqlRule.customQuery} `);
- cy.get(DEFINITION_STEP).eq(2).invoke('text').should('eql', 'Event Correlation');
- cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None');
-
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m');
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m');
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts
index b6b30ef550eb1..47e49d48e2aec 100644
--- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_ml.spec.ts
@@ -16,24 +16,14 @@ import {
SEVERITY,
} from '../screens/alerts_detection_rules';
import {
- ABOUT_FALSE_POSITIVES,
- ABOUT_MITRE,
- ABOUT_RISK,
ABOUT_RULE_DESCRIPTION,
- ABOUT_SEVERITY,
- ABOUT_STEP,
- ABOUT_TAGS,
- ABOUT_URLS,
- ANOMALY_SCORE,
- DEFINITION_TIMELINE,
- DEFINITION_STEP,
MACHINE_LEARNING_JOB_ID,
MACHINE_LEARNING_JOB_STATUS,
RULE_NAME_HEADER,
- SCHEDULE_LOOPBACK,
- SCHEDULE_RUNS,
- SCHEDULE_STEP,
- RULE_TYPE,
+ getDescriptionForTitle,
+ ABOUT_DETAILS,
+ DEFINITION_DETAILS,
+ SCHEDULE_DETAILS,
} from '../screens/rule_details';
import {
@@ -126,36 +116,37 @@ describe('Detection rules, machine learning', () => {
cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${machineLearningRule.name} Beta`);
cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', machineLearningRule.description);
- cy.get(ABOUT_STEP)
- .eq(ABOUT_SEVERITY)
- .invoke('text')
- .should('eql', machineLearningRule.severity);
- cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', machineLearningRule.riskScore);
- cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls);
- cy.get(ABOUT_STEP)
- .eq(ABOUT_FALSE_POSITIVES)
- .invoke('text')
- .should('eql', expectedFalsePositives);
- cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre);
- cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags);
-
- cy.get(DEFINITION_STEP).eq(RULE_TYPE).invoke('text').should('eql', 'Machine Learning');
- cy.get(DEFINITION_STEP)
- .eq(ANOMALY_SCORE)
- .invoke('text')
- .should('eql', machineLearningRule.anomalyScoreThreshold);
- cy.get(DEFINITION_STEP)
- .get(MACHINE_LEARNING_JOB_STATUS)
- .invoke('text')
- .should('eql', 'Stopped');
- cy.get(DEFINITION_STEP)
- .get(MACHINE_LEARNING_JOB_ID)
- .invoke('text')
- .should('eql', machineLearningRule.machineLearningJob);
-
- cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None');
-
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m');
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m');
+ cy.get(ABOUT_DETAILS).within(() => {
+ getDescriptionForTitle('Severity').invoke('text').should('eql', machineLearningRule.severity);
+ getDescriptionForTitle('Risk score')
+ .invoke('text')
+ .should('eql', machineLearningRule.riskScore);
+ getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls);
+ getDescriptionForTitle('False positive examples')
+ .invoke('text')
+ .should('eql', expectedFalsePositives);
+ getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre);
+ getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags);
+ });
+
+ cy.get(DEFINITION_DETAILS).within(() => {
+ getDescriptionForTitle('Anomaly score')
+ .invoke('text')
+ .should('eql', machineLearningRule.anomalyScoreThreshold);
+ getDescriptionForTitle('Anomaly score')
+ .invoke('text')
+ .should('eql', machineLearningRule.anomalyScoreThreshold);
+ getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Machine Learning');
+ getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None');
+ cy.get(MACHINE_LEARNING_JOB_STATUS).invoke('text').should('eql', 'Stopped');
+ cy.get(MACHINE_LEARNING_JOB_ID)
+ .invoke('text')
+ .should('eql', machineLearningRule.machineLearningJob);
+ });
+
+ cy.get(SCHEDULE_DETAILS).within(() => {
+ getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m');
+ getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m');
+ });
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts
index e3526c63e2310..4edf5e1866087 100644
--- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_override.spec.ts
@@ -16,29 +16,17 @@ import {
} from '../screens/alerts_detection_rules';
import {
ABOUT_INVESTIGATION_NOTES,
- ABOUT_OVERRIDE_FALSE_POSITIVES,
- ABOUT_OVERRIDE_MITRE,
- ABOUT_OVERRIDE_NAME_OVERRIDE,
- ABOUT_OVERRIDE_RISK,
- ABOUT_OVERRIDE_RISK_OVERRIDE,
- ABOUT_OVERRIDE_SEVERITY_OVERRIDE,
- ABOUT_OVERRIDE_TAGS,
- ABOUT_OVERRIDE_TIMESTAMP_OVERRIDE,
- ABOUT_OVERRIDE_URLS,
ABOUT_RULE_DESCRIPTION,
- ABOUT_SEVERITY,
- ABOUT_STEP,
- DEFINITION_CUSTOM_QUERY,
- DEFINITION_INDEX_PATTERNS,
- DEFINITION_TIMELINE,
- DEFINITION_STEP,
INVESTIGATION_NOTES_MARKDOWN,
INVESTIGATION_NOTES_TOGGLE,
RULE_ABOUT_DETAILS_HEADER_TOGGLE,
RULE_NAME_HEADER,
- SCHEDULE_LOOPBACK,
- SCHEDULE_RUNS,
- SCHEDULE_STEP,
+ ABOUT_DETAILS,
+ getDescriptionForTitle,
+ DEFINITION_DETAILS,
+ SCHEDULE_DETAILS,
+ DETAILS_TITLE,
+ DETAILS_DESCRIPTION,
} from '../screens/rule_details';
import {
@@ -141,56 +129,56 @@ describe('Detection rules, override', () => {
const expectedOverrideSeverities = ['Low', 'Medium', 'High', 'Critical'];
- cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', newOverrideRule.severity);
- newOverrideRule.severityOverride.forEach((severity, i) => {
- cy.get(ABOUT_STEP)
- .eq(ABOUT_OVERRIDE_SEVERITY_OVERRIDE + i)
+ cy.get(ABOUT_DETAILS).within(() => {
+ getDescriptionForTitle('Severity').invoke('text').should('eql', newOverrideRule.severity);
+ getDescriptionForTitle('Risk score').invoke('text').should('eql', newOverrideRule.riskScore);
+ getDescriptionForTitle('Risk score override')
.invoke('text')
- .should(
- 'eql',
- `${severity.sourceField}:${severity.sourceValue}${expectedOverrideSeverities[i]}`
- );
+ .should('eql', `${newOverrideRule.riskOverride}signal.rule.risk_score`);
+ getDescriptionForTitle('Rule name override')
+ .invoke('text')
+ .should('eql', newOverrideRule.nameOverride);
+ getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls);
+ getDescriptionForTitle('False positive examples')
+ .invoke('text')
+ .should('eql', expectedFalsePositives);
+ getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre);
+ getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags);
+ getDescriptionForTitle('Timestamp override')
+ .invoke('text')
+ .should('eql', newOverrideRule.timestampOverride);
+ cy.contains(DETAILS_TITLE, 'Severity override')
+ .invoke('index', DETAILS_TITLE) // get index relative to other titles, not all siblings
+ .then((severityOverrideIndex) => {
+ newOverrideRule.severityOverride.forEach((severity, i) => {
+ cy.get(DETAILS_DESCRIPTION)
+ .eq(severityOverrideIndex + i)
+ .invoke('text')
+ .should(
+ 'eql',
+ `${severity.sourceField}:${severity.sourceValue}${expectedOverrideSeverities[i]}`
+ );
+ });
+ });
});
- cy.get(ABOUT_STEP)
- .eq(ABOUT_OVERRIDE_RISK)
- .invoke('text')
- .should('eql', newOverrideRule.riskScore);
- cy.get(ABOUT_STEP)
- .eq(ABOUT_OVERRIDE_RISK_OVERRIDE)
- .invoke('text')
- .should('eql', `${newOverrideRule.riskOverride}signal.rule.risk_score`);
- cy.get(ABOUT_STEP).eq(ABOUT_OVERRIDE_URLS).invoke('text').should('eql', expectedUrls);
- cy.get(ABOUT_STEP)
- .eq(ABOUT_OVERRIDE_FALSE_POSITIVES)
- .invoke('text')
- .should('eql', expectedFalsePositives);
- cy.get(ABOUT_STEP)
- .eq(ABOUT_OVERRIDE_NAME_OVERRIDE)
- .invoke('text')
- .should('eql', newOverrideRule.nameOverride);
- cy.get(ABOUT_STEP).eq(ABOUT_OVERRIDE_MITRE).invoke('text').should('eql', expectedMitre);
- cy.get(ABOUT_STEP)
- .eq(ABOUT_OVERRIDE_TIMESTAMP_OVERRIDE)
- .invoke('text')
- .should('eql', newOverrideRule.timestampOverride);
- cy.get(ABOUT_STEP).eq(ABOUT_OVERRIDE_TAGS).invoke('text').should('eql', expectedTags);
-
cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN);
- cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => {
- cy.wrap(patterns).each((pattern, index) => {
- cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]);
- });
+ cy.get(DEFINITION_DETAILS).within(() => {
+ getDescriptionForTitle('Index patterns')
+ .invoke('text')
+ .should('eql', expectedIndexPatterns.join(''));
+ getDescriptionForTitle('Custom query')
+ .invoke('text')
+ .should('eql', `${newOverrideRule.customQuery} `);
+ getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Query');
+ getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None');
+ });
+
+ cy.get(SCHEDULE_DETAILS).within(() => {
+ getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m');
+ getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m');
});
- cy.get(DEFINITION_STEP)
- .eq(DEFINITION_CUSTOM_QUERY)
- .invoke('text')
- .should('eql', `${newOverrideRule.customQuery} `);
- cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None');
-
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m');
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m');
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts
index 10f9ebb5623df..00175ed3baeb8 100644
--- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_threshold.spec.ts
@@ -15,27 +15,16 @@ import {
SEVERITY,
} from '../screens/alerts_detection_rules';
import {
- ABOUT_FALSE_POSITIVES,
ABOUT_INVESTIGATION_NOTES,
- ABOUT_MITRE,
- ABOUT_RISK,
ABOUT_RULE_DESCRIPTION,
- ABOUT_SEVERITY,
- ABOUT_STEP,
- ABOUT_TAGS,
- ABOUT_URLS,
- DEFINITION_CUSTOM_QUERY,
- DEFINITION_INDEX_PATTERNS,
- DEFINITION_THRESHOLD,
- DEFINITION_TIMELINE,
- DEFINITION_STEP,
INVESTIGATION_NOTES_MARKDOWN,
INVESTIGATION_NOTES_TOGGLE,
RULE_ABOUT_DETAILS_HEADER_TOGGLE,
RULE_NAME_HEADER,
- SCHEDULE_LOOPBACK,
- SCHEDULE_RUNS,
- SCHEDULE_STEP,
+ getDescriptionForTitle,
+ ABOUT_DETAILS,
+ DEFINITION_DETAILS,
+ SCHEDULE_DETAILS,
} from '../screens/rule_details';
import {
@@ -137,38 +126,40 @@ describe('Detection rules, threshold', () => {
cy.get(RULE_NAME_HEADER).invoke('text').should('eql', `${newThresholdRule.name} Beta`);
cy.get(ABOUT_RULE_DESCRIPTION).invoke('text').should('eql', newThresholdRule.description);
- cy.get(ABOUT_STEP).eq(ABOUT_SEVERITY).invoke('text').should('eql', newThresholdRule.severity);
- cy.get(ABOUT_STEP).eq(ABOUT_RISK).invoke('text').should('eql', newThresholdRule.riskScore);
- cy.get(ABOUT_STEP).eq(ABOUT_URLS).invoke('text').should('eql', expectedUrls);
- cy.get(ABOUT_STEP)
- .eq(ABOUT_FALSE_POSITIVES)
- .invoke('text')
- .should('eql', expectedFalsePositives);
- cy.get(ABOUT_STEP).eq(ABOUT_MITRE).invoke('text').should('eql', expectedMitre);
- cy.get(ABOUT_STEP).eq(ABOUT_TAGS).invoke('text').should('eql', expectedTags);
+ cy.get(ABOUT_DETAILS).within(() => {
+ getDescriptionForTitle('Severity').invoke('text').should('eql', newThresholdRule.severity);
+ getDescriptionForTitle('Risk score').invoke('text').should('eql', newThresholdRule.riskScore);
+ getDescriptionForTitle('Reference URLs').invoke('text').should('eql', expectedUrls);
+ getDescriptionForTitle('False positive examples')
+ .invoke('text')
+ .should('eql', expectedFalsePositives);
+ getDescriptionForTitle('MITRE ATT&CK').invoke('text').should('eql', expectedMitre);
+ getDescriptionForTitle('Tags').invoke('text').should('eql', expectedTags);
+ });
cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE).eq(INVESTIGATION_NOTES_TOGGLE).click({ force: true });
cy.get(ABOUT_INVESTIGATION_NOTES).invoke('text').should('eql', INVESTIGATION_NOTES_MARKDOWN);
- cy.get(DEFINITION_INDEX_PATTERNS).then((patterns) => {
- cy.wrap(patterns).each((pattern, index) => {
- cy.wrap(pattern).invoke('text').should('eql', expectedIndexPatterns[index]);
- });
+ cy.get(DEFINITION_DETAILS).within(() => {
+ getDescriptionForTitle('Index patterns')
+ .invoke('text')
+ .should('eql', expectedIndexPatterns.join(''));
+ getDescriptionForTitle('Custom query')
+ .invoke('text')
+ .should('eql', `${newThresholdRule.customQuery} `);
+ getDescriptionForTitle('Rule type').invoke('text').should('eql', 'Threshold');
+ getDescriptionForTitle('Timeline template').invoke('text').should('eql', 'None');
+ getDescriptionForTitle('Threshold')
+ .invoke('text')
+ .should(
+ 'eql',
+ `Results aggregated by ${newThresholdRule.thresholdField} >= ${newThresholdRule.threshold}`
+ );
+ });
+
+ cy.get(SCHEDULE_DETAILS).within(() => {
+ getDescriptionForTitle('Runs every').invoke('text').should('eql', '5m');
+ getDescriptionForTitle('Additional look-back time').invoke('text').should('eql', '1m');
});
- cy.get(DEFINITION_STEP)
- .eq(DEFINITION_CUSTOM_QUERY)
- .invoke('text')
- .should('eql', `${newThresholdRule.customQuery} `);
- cy.get(DEFINITION_STEP).eq(DEFINITION_TIMELINE).invoke('text').should('eql', 'None');
- cy.get(DEFINITION_STEP)
- .eq(DEFINITION_THRESHOLD)
- .invoke('text')
- .should(
- 'eql',
- `Results aggregated by ${newThresholdRule.thresholdField} >= ${newThresholdRule.threshold}`
- );
-
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_RUNS).invoke('text').should('eql', '5m');
- cy.get(SCHEDULE_STEP).eq(SCHEDULE_LOOPBACK).invoke('text').should('eql', '1m');
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts
index 9438c28f05fef..6194d6892d799 100644
--- a/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/cases.spec.ts
@@ -40,7 +40,7 @@ import { TIMELINE_DESCRIPTION, TIMELINE_QUERY, TIMELINE_TITLE } from '../screens
import { goToCaseDetails, goToCreateNewCase } from '../tasks/all_cases';
import { openCaseTimeline } from '../tasks/case_details';
-import { backToCases, createNewCase } from '../tasks/create_new_case';
+import { backToCases, createNewCaseWithTimeline } from '../tasks/create_new_case';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
@@ -58,7 +58,7 @@ describe('Cases', () => {
it('Creates a new case with timeline and opens the timeline', () => {
loginAndWaitForPageWithoutDateRange(CASES_URL);
goToCreateNewCase();
- createNewCase(case1);
+ createNewCaseWithTimeline(case1);
backToCases();
cy.get(ALL_CASES_PAGE_TITLE).should('have.text', 'Cases Beta');
diff --git a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts
index 5f2de69689865..d193330dc54ff 100644
--- a/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/events_viewer.spec.ts
@@ -47,7 +47,8 @@ const defaultHeadersInDefaultEcsCategory = [
{ id: 'destination.ip' },
];
-describe('Events Viewer', () => {
+// https://github.com/elastic/kibana/issues/70757
+describe.skip('Events Viewer', () => {
context('Fields rendering', () => {
before(() => {
loginAndWaitForPage(HOSTS_URL);
diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts
new file mode 100644
index 0000000000000..6af4d174b9583
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/integration/timeline_attach_to_case.spec.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 { loginAndWaitForTimeline } from '../tasks/login';
+import {
+ attachTimelineToNewCase,
+ attachTimelineToExistingCase,
+ addNewCase,
+ selectCase,
+} from '../tasks/timeline';
+import { DESCRIPTION_INPUT } from '../screens/create_new_case';
+import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
+import { caseTimeline, TIMELINE_CASE_ID } from '../objects/case';
+
+describe('attach timeline to case', () => {
+ beforeEach(() => {
+ loginAndWaitForTimeline(caseTimeline.id);
+ });
+ context('without cases created', () => {
+ before(() => {
+ esArchiverLoad('timeline');
+ });
+
+ after(() => {
+ esArchiverUnload('timeline');
+ });
+
+ it('attach timeline to a new case', () => {
+ attachTimelineToNewCase();
+
+ cy.location('origin').then((origin) => {
+ cy.get(DESCRIPTION_INPUT).should(
+ 'have.text',
+ `[${caseTimeline.title}](${origin}/app/security/timelines?timeline=(id:'${caseTimeline.id}',isOpen:!t))`
+ );
+ });
+ });
+
+ it('attach timeline to an existing case with no case', () => {
+ attachTimelineToExistingCase();
+ addNewCase();
+
+ cy.location('origin').then((origin) => {
+ cy.get(DESCRIPTION_INPUT).should(
+ 'have.text',
+ `[${caseTimeline.title}](${origin}/app/security/timelines?timeline=(id:'${caseTimeline.id}',isOpen:!t))`
+ );
+ });
+ });
+ });
+
+ context('with cases created', () => {
+ before(() => {
+ esArchiverLoad('case_and_timeline');
+ });
+
+ after(() => {
+ esArchiverUnload('case_and_timeline');
+ });
+
+ it('attach timeline to an existing case', () => {
+ attachTimelineToExistingCase();
+ selectCase(TIMELINE_CASE_ID);
+
+ cy.location('origin').then((origin) => {
+ cy.get(DESCRIPTION_INPUT).should(
+ 'have.text',
+ `[${caseTimeline.title}](${origin}/app/security/timelines?timeline=(id:'${caseTimeline.id}',isOpen:!t))`
+ );
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/cypress/objects/case.ts b/x-pack/plugins/security_solution/cypress/objects/case.ts
index 12d3f925169af..084df31a604a3 100644
--- a/x-pack/plugins/security_solution/cypress/objects/case.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/case.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { Timeline } from './timeline';
+import { Timeline, TimelineWithId } from './timeline';
export interface TestCase {
name: string;
@@ -21,10 +21,11 @@ export interface Connector {
password: string;
}
-const caseTimeline: Timeline = {
+export const caseTimeline: TimelineWithId = {
title: 'SIEM test',
description: 'description',
query: 'host.name:*',
+ id: '0162c130-78be-11ea-9718-118a926974a4',
};
export const case1: TestCase = {
@@ -41,3 +42,5 @@ export const serviceNowConnector: Connector = {
username: 'Username Name',
password: 'password',
};
+
+export const TIMELINE_CASE_ID = '68248e00-f689-11ea-9ab2-59238b522856';
diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts
index 0624606fe8481..2a5c60815f450 100644
--- a/x-pack/plugins/security_solution/cypress/objects/rule.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts
@@ -27,6 +27,8 @@ export interface CustomRule {
customQuery: string;
name: string;
description: string;
+ index?: string[];
+ interval?: string;
severity: string;
riskScore: string;
tags: string[];
@@ -109,6 +111,29 @@ export const newRule: CustomRule = {
timelineId: '0162c130-78be-11ea-9718-118a926974a4',
};
+export const existingRule: CustomRule = {
+ customQuery: 'host.name:*',
+ name: 'Rule 1',
+ description: 'Description for Rule 1',
+ index: [
+ 'apm-*-transaction*',
+ 'auditbeat-*',
+ 'endgame-*',
+ 'filebeat-*',
+ 'packetbeat-*',
+ 'winlogbeat-*',
+ ],
+ interval: '4m',
+ severity: 'High',
+ riskScore: '19',
+ tags: ['rule1'],
+ referenceUrls: [],
+ falsePositivesExamples: [],
+ mitre: [],
+ note: 'This is my note',
+ timelineId: '',
+};
+
export const newOverrideRule: OverrideRule = {
customQuery: 'host.name:*',
name: 'New Rule Test',
diff --git a/x-pack/plugins/security_solution/cypress/objects/timeline.ts b/x-pack/plugins/security_solution/cypress/objects/timeline.ts
index 060a1376b46ce..ff7e80e5661ad 100644
--- a/x-pack/plugins/security_solution/cypress/objects/timeline.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/timeline.ts
@@ -9,3 +9,7 @@ export interface Timeline {
description: string;
query: string;
}
+
+export interface TimelineWithId extends Timeline {
+ id: string;
+}
diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts
index a41b8296f83e4..14f5383939a94 100644
--- a/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts
@@ -14,6 +14,8 @@ export const CUSTOM_RULES_BTN = '[data-test-subj="show-custom-rules-filter-butto
export const DELETE_RULE_ACTION_BTN = '[data-test-subj="deleteRuleAction"]';
+export const EDIT_RULE_ACTION_BTN = '[data-test-subj="editRuleAction"]';
+
export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]';
export const ELASTIC_RULES_BTN = '[data-test-subj="show-elastic-rules-filter-button"]';
diff --git a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts
index 4fa6b69eea7c3..dc0e764744f84 100644
--- a/x-pack/plugins/security_solution/cypress/screens/all_cases.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/all_cases.ts
@@ -4,6 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
+export const ALL_CASES_CASE = (id: string) => {
+ return `[data-test-subj="cases-table-row-${id}"]`;
+};
+
export const ALL_CASES_CLOSE_ACTION = '[data-test-subj="action-close"]';
export const ALL_CASES_CLOSED_CASES_COUNT = '[data-test-subj="closed-case-count"]';
@@ -14,6 +18,8 @@ export const ALL_CASES_COMMENTS_COUNT = '[data-test-subj="case-table-column-comm
export const ALL_CASES_CREATE_NEW_CASE_BTN = '[data-test-subj="createNewCaseBtn"]';
+export const ALL_CASES_CREATE_NEW_CASE_TABLE_BTN = '[data-test-subj="cases-table-add-case"]';
+
export const ALL_CASES_DELETE_ACTION = '[data-test-subj="action-delete"]';
export const ALL_CASES_NAME = '[data-test-subj="case-details-link"]';
diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_case.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_case.ts
index 6e2beb78fff19..9431c054d96a4 100644
--- a/x-pack/plugins/security_solution/cypress/screens/create_new_case.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/create_new_case.ts
@@ -6,8 +6,7 @@
export const BACK_TO_CASES_BTN = '[data-test-subj="backToCases"]';
-export const DESCRIPTION_INPUT =
- '[data-test-subj="caseDescription"] [data-test-subj="textAreaInput"]';
+export const DESCRIPTION_INPUT = '[data-test-subj="textAreaInput"]';
export const INSERT_TIMELINE_BTN = '[data-test-subj="insert-timeline-button"]';
diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
index 1c25ed88c3bee..dda371126d5aa 100644
--- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
@@ -8,6 +8,13 @@ export const ABOUT_CONTINUE_BTN = '[data-test-subj="about-continue"]';
export const ABOUT_EDIT_BUTTON = '[data-test-subj="edit-about-rule"]';
+export const ABOUT_EDIT_TAB = '[data-test-subj="edit-rule-about-tab"]';
+
+export const ACTIONS_EDIT_TAB = '[data-test-subj="edit-rule-actions-tab"]';
+
+export const ACTIONS_THROTTLE_INPUT =
+ '[data-test-subj="stepRuleActions"] [data-test-subj="select"]';
+
export const ADD_FALSE_POSITIVE_BTN =
'[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text';
@@ -30,6 +37,11 @@ export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]';
export const DEFINE_EDIT_BUTTON = '[data-test-subj="edit-define-rule"]';
+export const DEFINE_EDIT_TAB = '[data-test-subj="edit-rule-define-tab"]';
+
+export const DEFINE_INDEX_INPUT =
+ '[data-test-subj="detectionEngineStepDefineRuleIndices"] [data-test-subj="input"]';
+
export const EQL_TYPE = '[data-test-subj="eqlRuleType"]';
export const EQL_QUERY_INPUT = '[data-test-subj="eqlQueryBarTextInput"]';
@@ -81,6 +93,20 @@ export const RULE_TIMESTAMP_OVERRIDE =
export const SCHEDULE_CONTINUE_BUTTON = '[data-test-subj="schedule-continue"]';
+export const SCHEDULE_EDIT_TAB = '[data-test-subj="edit-rule-schedule-tab"]';
+
+export const SCHEDULE_INTERVAL_AMOUNT_INPUT =
+ '[data-test-subj="detectionEngineStepScheduleRuleInterval"] [data-test-subj="schedule-amount-input"]';
+
+export const SCHEDULE_INTERVAL_UNITS_INPUT =
+ '[data-test-subj="detectionEngineStepScheduleRuleInterval"] [data-test-subj="schedule-units-input"]';
+
+export const SCHEDULE_LOOKBACK_AMOUNT_INPUT =
+ '[data-test-subj="detectionEngineStepScheduleRuleFrom"] [data-test-subj="schedule-amount-input"]';
+
+export const SCHEDULE_LOOKBACK_UNITS_INPUT =
+ '[data-test-subj="detectionEngineStepScheduleRuleFrom"] [data-test-subj="schedule-units-input"]';
+
export const SEVERITY_DROPDOWN =
'[data-test-subj="detectionEngineStepAboutRuleSeverity"] [data-test-subj="select"]';
@@ -88,6 +114,9 @@ export const SEVERITY_MAPPING_OVERRIDE_OPTION = '#severity-mapping-override';
export const SEVERITY_OVERRIDE_ROW = '[data-test-subj="severityOverrideRow"]';
+export const TAGS_FIELD =
+ '[data-test-subj="detectionEngineStepAboutRuleTags"] [data-test-subj="comboBoxInput"]';
+
export const TAGS_INPUT =
'[data-test-subj="detectionEngineStepAboutRuleTags"] [data-test-subj="comboBoxSearchInput"]';
diff --git a/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts b/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts
new file mode 100644
index 0000000000000..1bf0ff34ebd94
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/screens/edit_rule.ts
@@ -0,0 +1,7 @@
+/*
+ * 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.
+ */
+
+export const EDIT_SUBMIT_BUTTON = '[data-test-subj="ruleEditSubmitButton"]';
diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts
index b221709966943..98fc7b06a9908 100644
--- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts
@@ -4,55 +4,21 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const ABOUT_FALSE_POSITIVES = 3;
+export const getDescriptionForTitle = (title: string) =>
+ cy.get(DETAILS_TITLE).contains(title).next(DETAILS_DESCRIPTION);
-export const ABOUT_INVESTIGATION_NOTES = '[data-test-subj="stepAboutDetailsNoteContent"]';
-
-export const ABOUT_MITRE = 4;
-
-export const ABOUT_OVERRIDE_FALSE_POSITIVES = 8;
-
-export const ABOUT_OVERRIDE_MITRE = 10;
-
-export const ABOUT_OVERRIDE_NAME_OVERRIDE = 9;
-
-export const ABOUT_OVERRIDE_RISK = 5;
+export const DETAILS_DESCRIPTION = '.euiDescriptionList__description';
+export const DETAILS_TITLE = '.euiDescriptionList__title';
-export const ABOUT_OVERRIDE_RISK_OVERRIDE = 6;
-
-export const ABOUT_OVERRIDE_SEVERITY_OVERRIDE = 1;
-
-export const ABOUT_OVERRIDE_TAGS = 12;
-
-export const ABOUT_OVERRIDE_TIMESTAMP_OVERRIDE = 11;
-
-export const ABOUT_OVERRIDE_URLS = 7;
+export const ABOUT_INVESTIGATION_NOTES = '[data-test-subj="stepAboutDetailsNoteContent"]';
export const ABOUT_RULE_DESCRIPTION = '[data-test-subj=stepAboutRuleDetailsToggleDescriptionText]';
-export const ABOUT_RISK = 1;
+export const ABOUT_DETAILS =
+ '[data-test-subj="aboutRule"] [data-test-subj="listItemColumnStepRuleDescription"]';
-export const ABOUT_SEVERITY = 0;
-
-export const ABOUT_STEP = '[data-test-subj="aboutRule"] .euiDescriptionList__description';
-
-export const ABOUT_TAGS = 5;
-
-export const ABOUT_URLS = 2;
-
-export const ANOMALY_SCORE = 1;
-
-export const DEFINITION_CUSTOM_QUERY = 1;
-
-export const DEFINITION_THRESHOLD = 4;
-
-export const DEFINITION_TIMELINE = 3;
-
-export const DEFINITION_INDEX_PATTERNS =
- '[data-test-subj=definitionRule] [data-test-subj="listItemColumnStepRuleDescription"] .euiDescriptionList__description .euiBadge__text';
-
-export const DEFINITION_STEP =
- '[data-test-subj=definitionRule] [data-test-subj="listItemColumnStepRuleDescription"] .euiDescriptionList__description';
+export const DEFINITION_DETAILS =
+ '[data-test-subj=definitionRule] [data-test-subj="listItemColumnStepRuleDescription"]';
export const INVESTIGATION_NOTES_MARKDOWN = 'test markdown';
@@ -60,16 +26,13 @@ export const INVESTIGATION_NOTES_TOGGLE = 1;
export const MACHINE_LEARNING_JOB_ID = '[data-test-subj="machineLearningJobId"]';
-export const MACHINE_LEARNING_JOB_STATUS = '[data-test-subj="machineLearningJobStatus" ]';
+export const MACHINE_LEARNING_JOB_STATUS = '[data-test-subj="machineLearningJobStatus"]';
export const RULE_ABOUT_DETAILS_HEADER_TOGGLE = '[data-test-subj="stepAboutDetailsToggle"]';
export const RULE_NAME_HEADER = '[data-test-subj="header-page-title"]';
-export const RULE_TYPE = 0;
+export const SCHEDULE_DETAILS =
+ '[data-test-subj=schedule] [data-test-subj="listItemColumnStepRuleDescription"]';
export const SCHEDULE_STEP = '[data-test-subj="schedule"] .euiDescriptionList__description';
-
-export const SCHEDULE_RUNS = 0;
-
-export const SCHEDULE_LOOPBACK = 1;
diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts
index fd41cd63fc090..bcb64fc947feb 100644
--- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts
@@ -4,8 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
+export const ATTACH_TIMELINE_TO_NEW_CASE_ICON = '[data-test-subj="attach-timeline-case"]';
+
+export const ATTACH_TIMELINE_TO_EXISTING_CASE_ICON =
+ '[data-test-subj="attach-timeline-existing-case"]';
+
export const BULK_ACTIONS = '[data-test-subj="utility-bar-action-button"]';
+export const CASE = (id: string) => {
+ return `[data-test-subj="cases-table-row-${id}"]`;
+};
+
export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]';
export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]';
@@ -25,6 +34,8 @@ export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name
export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]';
+export const OPEN_TIMELINE_ICON = '[data-test-subj="open-timeline-button"]';
+
export const PIN_EVENT = '[data-test-subj="pin"]';
export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]';
diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts
index 5ec5bb97250db..c530594508f95 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts
@@ -24,6 +24,7 @@ import {
SORT_RULES_BTN,
THREE_HUNDRED_ROWS,
EXPORT_ACTION_BTN,
+ EDIT_RULE_ACTION_BTN,
} from '../screens/alerts_detection_rules';
export const activateRule = (rulePosition: number) => {
@@ -35,6 +36,11 @@ export const changeToThreeHundredRowsPerPage = () => {
cy.get(THREE_HUNDRED_ROWS).click();
};
+export const editFirstRule = () => {
+ cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true });
+ cy.get(EDIT_RULE_ACTION_BTN).click();
+};
+
export const deleteFirstRule = () => {
cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true });
cy.get(DELETE_RULE_ACTION_BTN).click();
diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts
index b2cde23a8dce2..1d5d240c5c53d 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_case.ts
@@ -29,6 +29,18 @@ export const createNewCase = (newCase: TestCase) => {
});
cy.get(DESCRIPTION_INPUT).type(`${newCase.description} `, { force: true });
+ cy.get(SUBMIT_BTN).click({ force: true });
+ cy.get(LOADING_SPINNER).should('exist');
+ cy.get(LOADING_SPINNER).should('not.exist');
+};
+
+export const createNewCaseWithTimeline = (newCase: TestCase) => {
+ cy.get(TITLE_INPUT).type(newCase.name, { force: true });
+ newCase.tags.forEach((tag) => {
+ cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true });
+ });
+ cy.get(DESCRIPTION_INPUT).type(`${newCase.description} `, { force: true });
+
cy.get(INSERT_TIMELINE_BTN).click({ force: true });
cy.get(TIMELINE_SEARCHBOX).type(`${newCase.timeline.title}{enter}`);
cy.get(TIMELINE).should('be.visible');
diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
index f26a77171462c..0daff52de7063 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
@@ -13,14 +13,17 @@ import {
} from '../objects/rule';
import {
ABOUT_CONTINUE_BTN,
- ANOMALY_THRESHOLD_INPUT,
+ ABOUT_EDIT_TAB,
+ ACTIONS_EDIT_TAB,
ADD_FALSE_POSITIVE_BTN,
ADD_REFERENCE_URL_BTN,
ADVANCED_SETTINGS_BTN,
+ ANOMALY_THRESHOLD_INPUT,
COMBO_BOX_INPUT,
CREATE_AND_ACTIVATE_BTN,
CUSTOM_QUERY_INPUT,
DEFINE_CONTINUE_BUTTON,
+ DEFINE_EDIT_TAB,
FALSE_POSITIVES_INPUT,
IMPORT_QUERY_FROM_SAVED_TIMELINE_LINK,
INPUT,
@@ -32,8 +35,8 @@ import {
MITRE_TACTIC,
MITRE_TACTIC_DROPDOWN,
MITRE_TECHNIQUES_INPUT,
- RISK_INPUT,
REFERENCE_URLS_INPUT,
+ RISK_INPUT,
RISK_MAPPING_OVERRIDE_OPTION,
RISK_OVERRIDE,
RULE_DESCRIPTION_INPUT,
@@ -41,6 +44,7 @@ import {
RULE_NAME_OVERRIDE,
RULE_TIMESTAMP_OVERRIDE,
SCHEDULE_CONTINUE_BUTTON,
+ SCHEDULE_EDIT_TAB,
SEVERITY_DROPDOWN,
SEVERITY_MAPPING_OVERRIDE_OPTION,
SEVERITY_OVERRIDE_ROW,
@@ -48,8 +52,6 @@ import {
THRESHOLD_FIELD_SELECTION,
THRESHOLD_INPUT_AREA,
THRESHOLD_TYPE,
- DEFINE_EDIT_BUTTON,
- ABOUT_EDIT_BUTTON,
EQL_TYPE,
EQL_QUERY_INPUT,
} from '../screens/create_new_rule';
@@ -61,11 +63,9 @@ export const createAndActivateRule = () => {
cy.get(CREATE_AND_ACTIVATE_BTN).should('not.exist');
};
-export const fillAboutRuleAndContinue = (
- rule: CustomRule | MachineLearningRule | ThresholdRule
-) => {
- cy.get(RULE_NAME_INPUT).type(rule.name, { force: true });
- cy.get(RULE_DESCRIPTION_INPUT).type(rule.description, { force: true });
+export const fillAboutRule = (rule: CustomRule | MachineLearningRule | ThresholdRule) => {
+ cy.get(RULE_NAME_INPUT).clear({ force: true }).type(rule.name, { force: true });
+ cy.get(RULE_DESCRIPTION_INPUT).clear({ force: true }).type(rule.description, { force: true });
cy.get(SEVERITY_DROPDOWN).click({ force: true });
cy.get(`#${rule.severity.toLowerCase()}`).click();
@@ -79,12 +79,15 @@ export const fillAboutRuleAndContinue = (
cy.get(ADVANCED_SETTINGS_BTN).click({ force: true });
rule.referenceUrls.forEach((url, index) => {
- cy.get(REFERENCE_URLS_INPUT).eq(index).type(url, { force: true });
+ cy.get(REFERENCE_URLS_INPUT).eq(index).clear({ force: true }).type(url, { force: true });
cy.get(ADD_REFERENCE_URL_BTN).click({ force: true });
});
rule.falsePositivesExamples.forEach((falsePositive, index) => {
- cy.get(FALSE_POSITIVES_INPUT).eq(index).type(falsePositive, { force: true });
+ cy.get(FALSE_POSITIVES_INPUT)
+ .eq(index)
+ .clear({ force: true })
+ .type(falsePositive, { force: true });
cy.get(ADD_FALSE_POSITIVE_BTN).click({ force: true });
});
@@ -93,14 +96,22 @@ export const fillAboutRuleAndContinue = (
cy.contains(MITRE_TACTIC, mitre.tactic).click();
mitre.techniques.forEach((technique) => {
- cy.get(MITRE_TECHNIQUES_INPUT).eq(index).type(`${technique}{enter}`, { force: true });
+ cy.get(MITRE_TECHNIQUES_INPUT)
+ .eq(index)
+ .clear({ force: true })
+ .type(`${technique}{enter}`, { force: true });
});
cy.get(MITRE_BTN).click({ force: true });
});
- cy.get(INVESTIGATION_NOTES_TEXTAREA).type(rule.note, { force: true });
+ cy.get(INVESTIGATION_NOTES_TEXTAREA).clear({ force: true }).type(rule.note, { force: true });
+};
+export const fillAboutRuleAndContinue = (
+ rule: CustomRule | MachineLearningRule | ThresholdRule
+) => {
+ fillAboutRule(rule);
cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true });
};
@@ -179,20 +190,6 @@ export const fillDefineCustomRuleWithImportedQueryAndContinue = (
cy.get(CUSTOM_QUERY_INPUT).should('not.exist');
};
-export const expectDefineFormToRepopulateAndContinue = (rule: CustomRule) => {
- cy.get(DEFINE_EDIT_BUTTON).click();
- cy.get(CUSTOM_QUERY_INPUT).invoke('text').should('eq', rule.customQuery);
- cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
- cy.get(DEFINE_CONTINUE_BUTTON).should('not.exist');
-};
-
-export const expectAboutFormToRepopulateAndContinue = (rule: CustomRule) => {
- cy.get(ABOUT_EDIT_BUTTON).click();
- cy.get(RULE_NAME_INPUT).invoke('val').should('eq', rule.name);
- cy.get(ABOUT_CONTINUE_BTN).should('exist').click({ force: true });
- cy.get(ABOUT_CONTINUE_BTN).should('not.exist');
-};
-
export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => {
const thresholdField = 0;
const threshold = 1;
@@ -230,6 +227,22 @@ export const fillDefineMachineLearningRuleAndContinue = (rule: MachineLearningRu
cy.get(MACHINE_LEARNING_DROPDOWN).should('not.exist');
};
+export const goToDefineStepTab = () => {
+ cy.get(DEFINE_EDIT_TAB).click({ force: true });
+};
+
+export const goToAboutStepTab = () => {
+ cy.get(ABOUT_EDIT_TAB).click({ force: true });
+};
+
+export const goToScheduleStepTab = () => {
+ cy.get(SCHEDULE_EDIT_TAB).click({ force: true });
+};
+
+export const goToActionsStepTab = () => {
+ cy.get(ACTIONS_EDIT_TAB).click({ force: true });
+};
+
export const selectMachineLearningRuleType = () => {
cy.get(MACHINE_LEARNING_TYPE).click({ force: true });
};
diff --git a/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts
new file mode 100644
index 0000000000000..690a36058ec33
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts
@@ -0,0 +1,12 @@
+/*
+ * 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 { EDIT_SUBMIT_BUTTON } from '../screens/edit_rule';
+
+export const saveEditedRule = () => {
+ cy.get(EDIT_SUBMIT_BUTTON).should('exist').click({ force: true });
+ cy.get(EDIT_SUBMIT_BUTTON).should('not.exist');
+};
diff --git a/x-pack/plugins/security_solution/cypress/tasks/login.ts b/x-pack/plugins/security_solution/cypress/tasks/login.ts
index ca23a1defd4f5..65f821ec5bfb7 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/login.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/login.ts
@@ -5,6 +5,7 @@
*/
import * as yaml from 'js-yaml';
+import { TIMELINE_FLYOUT_BODY } from '../screens/timeline';
/**
* Credentials in the `kibana.dev.yml` config file will be used to authenticate
@@ -143,3 +144,11 @@ export const loginAndWaitForPageWithoutDateRange = (url: string) => {
cy.visit(url);
cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 });
};
+
+export const loginAndWaitForTimeline = (timelineId: string) => {
+ login();
+ cy.viewport('macbook-15');
+ cy.visit(`/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`);
+ cy.get('[data-test-subj="headerGlobalNav"]');
+ cy.get(TIMELINE_FLYOUT_BODY).should('be.visible');
+};
diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts
index 6fb8bb5e29ae5..cd8b197fc4dec 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { ALL_CASES_CREATE_NEW_CASE_TABLE_BTN } from '../screens/all_cases';
import {
BULK_ACTIONS,
CLOSE_TIMELINE_BTN,
@@ -28,6 +29,10 @@ import {
TOGGLE_TIMELINE_EXPAND_EVENT,
REMOVE_COLUMN,
RESET_FIELDS,
+ ATTACH_TIMELINE_TO_NEW_CASE_ICON,
+ OPEN_TIMELINE_ICON,
+ ATTACH_TIMELINE_TO_EXISTING_CASE_ICON,
+ CASE,
} from '../screens/timeline';
import { drag, drop } from '../tasks/common';
@@ -44,6 +49,20 @@ export const addNameToTimeline = (name: string) => {
cy.get(TIMELINE_TITLE).should('have.attr', 'value', name);
};
+export const addNewCase = () => {
+ cy.get(ALL_CASES_CREATE_NEW_CASE_TABLE_BTN).click();
+};
+
+export const attachTimelineToNewCase = () => {
+ cy.get(TIMELINE_SETTINGS_ICON).click({ force: true });
+ cy.get(ATTACH_TIMELINE_TO_NEW_CASE_ICON).click({ force: true });
+};
+
+export const attachTimelineToExistingCase = () => {
+ cy.get(TIMELINE_SETTINGS_ICON).click({ force: true });
+ cy.get(ATTACH_TIMELINE_TO_EXISTING_CASE_ICON).click({ force: true });
+};
+
export const checkIdToggleField = () => {
cy.get(ID_HEADER_FIELD).should('not.exist');
@@ -85,6 +104,11 @@ export const openTimelineInspectButton = () => {
cy.get(TIMELINE_INSPECT_BUTTON).trigger('click', { force: true });
};
+export const openTimelineFromSettings = () => {
+ cy.get(TIMELINE_SETTINGS_ICON).click({ force: true });
+ cy.get(OPEN_TIMELINE_ICON).click({ force: true });
+};
+
export const openTimelineSettings = () => {
cy.get(TIMELINE_SETTINGS_ICON).trigger('click', { force: true });
};
@@ -132,6 +156,10 @@ export const resetFields = () => {
cy.get(RESET_FIELDS).click({ force: true });
};
+export const selectCase = (caseId: string) => {
+ cy.get(CASE(caseId)).click();
+};
+
export const waitForTimelinesPanelToBeLoaded = () => {
cy.get(TIMELINES_TABLE).should('exist');
};
diff --git a/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts b/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts
index 96c1217577ff2..87f8f46affb52 100644
--- a/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts
+++ b/x-pack/plugins/security_solution/public/cases/components/__mock__/form.ts
@@ -4,9 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form';
+import { useFormData } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data';
+
jest.mock(
'../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
);
+jest.mock(
+ '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'
+);
+
export const mockFormHook = {
isSubmitted: false,
isSubmitting: false,
@@ -41,3 +47,4 @@ export const getFormMock = (sampleData: any) => ({
});
export const useFormMock = useForm as jest.Mock;
+export const useFormDataMock = useFormData as jest.Mock;
diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx
index f697ce443f2c5..a800bd690f710 100644
--- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx
@@ -15,6 +15,7 @@ import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router
import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline';
import { usePostComment } from '../../containers/use_post_comment';
import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form';
+import { useFormData } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data';
// we don't have the types for waitFor just yet, so using "as waitFor" until when we do
import { wait as waitFor } from '@testing-library/react';
@@ -23,10 +24,15 @@ jest.mock(
'../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'
);
+jest.mock(
+ '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'
+);
+
jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline');
jest.mock('../../containers/use_post_comment');
-export const useFormMock = useForm as jest.Mock;
+const useFormMock = useForm as jest.Mock;
+const useFormDataMock = useFormData as jest.Mock;
const useInsertTimelineMock = useInsertTimeline as jest.Mock;
const usePostCommentMock = usePostComment as jest.Mock;
@@ -73,6 +79,7 @@ describe('AddComment ', () => {
useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline);
usePostCommentMock.mockImplementation(() => defaultPostCommment);
useFormMock.mockImplementation(() => ({ form: formHookMock }));
+ useFormDataMock.mockImplementation(() => [{ comment: sampleData.comment }]);
jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation);
});
diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx
index 87bd7bb247056..ef13c87a92dbb 100644
--- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx
@@ -14,7 +14,7 @@ import { Case } from '../../containers/types';
import { MarkdownEditorForm } from '../../../common/components/markdown_editor/form';
import { InsertTimelinePopover } from '../../../timelines/components/timeline/insert_timeline_popover';
import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline';
-import { Form, useForm, UseField } from '../../../shared_imports';
+import { Form, useForm, UseField, useFormData } from '../../../shared_imports';
import * as i18n from './translations';
import { schema } from './schema';
@@ -46,23 +46,31 @@ export const AddComment = React.memo(
forwardRef(
({ caseId, disabled, showLoading = true, onCommentPosted, onCommentSaving }, ref) => {
const { isLoading, postComment } = usePostComment(caseId);
+
const { form } = useForm({
defaultValue: initialCommentValue,
options: { stripEmptyFields: false },
schema,
});
- const { getFormData, setFieldValue, reset, submit } = form;
- const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline(
- form,
- 'comment'
+
+ const fieldName = 'comment';
+ const { setFieldValue, reset, submit } = form;
+ const [{ comment }] = useFormData({ form, watch: [fieldName] });
+
+ const onCommentChange = useCallback((newValue) => setFieldValue(fieldName, newValue), [
+ setFieldValue,
+ ]);
+
+ const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline(
+ comment,
+ onCommentChange
);
const addQuote = useCallback(
(quote) => {
- const { comment } = getFormData();
- setFieldValue('comment', `${comment}${comment.length > 0 ? '\n\n' : ''}${quote}`);
+ setFieldValue(fieldName, `${comment}${comment.length > 0 ? '\n\n' : ''}${quote}`);
},
- [getFormData, setFieldValue]
+ [comment, setFieldValue]
);
useImperativeHandle(ref, () => ({
@@ -87,7 +95,7 @@ export const AddComment = React.memo(
{isLoading && showLoading && }