-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Ingest pipelines] Add support for URI parts processor (#86163)
- Loading branch information
1 parent
c733233
commit 2b98dc6
Showing
7 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
136 changes: 136 additions & 0 deletions
136
...plication/components/pipeline_processors_editor/__jest__/processors/processor.helpers.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
import { act } from 'react-dom/test-utils'; | ||
import React from 'react'; | ||
import axios from 'axios'; | ||
import axiosXhrAdapter from 'axios/lib/adapters/xhr'; | ||
|
||
/* eslint-disable @kbn/eslint/no-restricted-paths */ | ||
import { usageCollectionPluginMock } from 'src/plugins/usage_collection/public/mocks'; | ||
|
||
import { registerTestBed, TestBed } from '@kbn/test/jest'; | ||
import { stubWebWorker } from '@kbn/test/jest'; | ||
import { uiMetricService, apiService } from '../../../../services'; | ||
import { Props } from '../../'; | ||
import { initHttpRequests } from '../http_requests.helpers'; | ||
import { ProcessorsEditorWithDeps } from '../processors_editor'; | ||
|
||
stubWebWorker(); | ||
|
||
jest.mock('../../../../../../../../../src/plugins/kibana_react/public', () => { | ||
const original = jest.requireActual('../../../../../../../../../src/plugins/kibana_react/public'); | ||
return { | ||
...original, | ||
// Mocking CodeEditor, which uses React Monaco under the hood | ||
CodeEditor: (props: any) => ( | ||
<input | ||
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'} | ||
data-currentvalue={props.value} | ||
onChange={(e: any) => { | ||
props.onChange(e.jsonContent); | ||
}} | ||
/> | ||
), | ||
}; | ||
}); | ||
|
||
jest.mock('@elastic/eui', () => { | ||
const original = jest.requireActual('@elastic/eui'); | ||
return { | ||
...original, | ||
// Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions, | ||
// which does not produce a valid component wrapper | ||
EuiComboBox: (props: any) => ( | ||
<input | ||
data-test-subj={props['data-test-subj']} | ||
data-currentvalue={props.selectedOptions} | ||
onChange={async (syntheticEvent: any) => { | ||
props.onChange([syntheticEvent['0']]); | ||
}} | ||
/> | ||
), | ||
}; | ||
}); | ||
|
||
jest.mock('react-virtualized', () => { | ||
const original = jest.requireActual('react-virtualized'); | ||
|
||
return { | ||
...original, | ||
AutoSizer: ({ children }: { children: any }) => ( | ||
<div>{children({ height: 500, width: 500 })}</div> | ||
), | ||
}; | ||
}); | ||
|
||
const testBedSetup = registerTestBed<TestSubject>( | ||
(props: Props) => <ProcessorsEditorWithDeps {...props} />, | ||
{ | ||
doMountAsync: false, | ||
} | ||
); | ||
|
||
export interface SetupResult extends TestBed<TestSubject> { | ||
actions: ReturnType<typeof createActions>; | ||
} | ||
|
||
const createActions = (testBed: TestBed<TestSubject>) => { | ||
const { find, component } = testBed; | ||
|
||
return { | ||
async saveNewProcessor() { | ||
await act(async () => { | ||
find('addProcessorForm.submitButton').simulate('click'); | ||
}); | ||
component.update(); | ||
}, | ||
|
||
async addProcessorType({ type, label }: { type: string; label: string }) { | ||
await act(async () => { | ||
find('processorTypeSelector.input').simulate('change', [{ value: type, label }]); | ||
}); | ||
component.update(); | ||
}, | ||
|
||
addProcessor() { | ||
find('addProcessorButton').simulate('click'); | ||
}, | ||
}; | ||
}; | ||
|
||
export const setup = async (props: Props): Promise<SetupResult> => { | ||
const testBed = await testBedSetup(props); | ||
return { | ||
...testBed, | ||
actions: createActions(testBed), | ||
}; | ||
}; | ||
|
||
const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); | ||
|
||
export const setupEnvironment = () => { | ||
// Initialize mock services | ||
uiMetricService.setup(usageCollectionPluginMock.createSetupContract()); | ||
// @ts-ignore | ||
apiService.setup(mockHttpClient, uiMetricService); | ||
|
||
const { server, httpRequestsMockHelpers } = initHttpRequests(); | ||
|
||
return { | ||
server, | ||
httpRequestsMockHelpers, | ||
}; | ||
}; | ||
|
||
type TestSubject = | ||
| 'addProcessorForm.submitButton' | ||
| 'addProcessorButton' | ||
| 'addProcessorForm.submitButton' | ||
| 'processorTypeSelector.input' | ||
| 'fieldNameField.input' | ||
| 'targetField.input' | ||
| 'keepOriginalField.input' | ||
| 'removeIfSuccessfulField.input'; |
123 changes: 123 additions & 0 deletions
123
.../application/components/pipeline_processors_editor/__jest__/processors/uri_parts.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
import { act } from 'react-dom/test-utils'; | ||
import { setup, SetupResult } from './processor.helpers'; | ||
|
||
// Default parameter values automatically added to the URI parts processor when saved | ||
const defaultUriPartsParameters = { | ||
keep_original: undefined, | ||
remove_if_successful: undefined, | ||
ignore_failure: undefined, | ||
description: undefined, | ||
}; | ||
|
||
describe('Processor: URI parts', () => { | ||
let onUpdate: jest.Mock; | ||
let testBed: SetupResult; | ||
|
||
beforeAll(() => { | ||
jest.useFakeTimers(); | ||
}); | ||
|
||
afterAll(() => { | ||
jest.useRealTimers(); | ||
}); | ||
|
||
beforeEach(async () => { | ||
onUpdate = jest.fn(); | ||
|
||
await act(async () => { | ||
testBed = await setup({ | ||
value: { | ||
processors: [], | ||
}, | ||
onFlyoutOpen: jest.fn(), | ||
onUpdate, | ||
}); | ||
}); | ||
testBed.component.update(); | ||
}); | ||
|
||
test('prevents form submission if required fields are not provided', async () => { | ||
const { | ||
actions: { addProcessor, saveNewProcessor, addProcessorType }, | ||
form, | ||
} = testBed; | ||
|
||
// Open flyout to add new processor | ||
addProcessor(); | ||
// Click submit button without entering any fields | ||
await saveNewProcessor(); | ||
|
||
// Expect form error as a processor type is required | ||
expect(form.getErrorsMessages()).toEqual(['A type is required.']); | ||
|
||
// Add type (the other fields are not visible until a type is selected) | ||
await addProcessorType({ type: 'uri_parts', label: 'URI parts' }); | ||
|
||
// Click submit button with only the type defined | ||
await saveNewProcessor(); | ||
|
||
// Expect form error as "field" is required parameter | ||
expect(form.getErrorsMessages()).toEqual(['A field value is required.']); | ||
}); | ||
|
||
test('saves with default parameter values', async () => { | ||
const { | ||
actions: { addProcessor, saveNewProcessor, addProcessorType }, | ||
form, | ||
} = testBed; | ||
|
||
// Open flyout to add new processor | ||
addProcessor(); | ||
// Add type (the other fields are not visible until a type is selected) | ||
await addProcessorType({ type: 'uri_parts', label: 'URI parts' }); | ||
// Add "field" value (required) | ||
form.setInputValue('fieldNameField.input', 'field_1'); | ||
// Save the field | ||
await saveNewProcessor(); | ||
|
||
const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1]; | ||
const { processors } = onUpdateResult.getData(); | ||
expect(processors[0].uri_parts).toEqual({ | ||
field: 'field_1', | ||
...defaultUriPartsParameters, | ||
}); | ||
}); | ||
|
||
test('allows optional parameters to be set', async () => { | ||
const { | ||
actions: { addProcessor, addProcessorType, saveNewProcessor }, | ||
form, | ||
} = testBed; | ||
|
||
// Open flyout to add new processor | ||
addProcessor(); | ||
// Add type (the other fields are not visible until a type is selected) | ||
await addProcessorType({ type: 'uri_parts', label: 'URI parts' }); | ||
// Add "field" value (required) | ||
form.setInputValue('fieldNameField.input', 'field_1'); | ||
|
||
// Set optional parameteres | ||
form.setInputValue('targetField.input', 'target_field'); | ||
form.toggleEuiSwitch('keepOriginalField.input'); | ||
form.toggleEuiSwitch('removeIfSuccessfulField.input'); | ||
|
||
// Save the field with new changes | ||
await saveNewProcessor(); | ||
|
||
const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1]; | ||
const { processors } = onUpdateResult.getData(); | ||
expect(processors[0].uri_parts).toEqual({ | ||
description: undefined, | ||
field: 'field_1', | ||
ignore_failure: undefined, | ||
keep_original: false, | ||
remove_if_successful: true, | ||
target_field: 'target_field', | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
.../components/pipeline_processors_editor/components/processor_form/processors/uri_parts.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
* 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 { FormattedMessage } from '@kbn/i18n/react'; | ||
import { EuiCode } from '@elastic/eui'; | ||
|
||
import { FIELD_TYPES, UseField, ToggleField } from '../../../../../../shared_imports'; | ||
|
||
import { FieldsConfig, to, from } from './shared'; | ||
|
||
import { FieldNameField } from './common_fields/field_name_field'; | ||
import { TargetField } from './common_fields/target_field'; | ||
|
||
export const fieldsConfig: FieldsConfig = { | ||
keep_original: { | ||
type: FIELD_TYPES.TOGGLE, | ||
defaultValue: true, | ||
deserializer: to.booleanOrUndef, | ||
serializer: from.undefinedIfValue(true), | ||
label: i18n.translate( | ||
'xpack.ingestPipelines.pipelineEditor.commonFields.keepOriginalFieldLabel', | ||
{ | ||
defaultMessage: 'Keep original', | ||
} | ||
), | ||
helpText: ( | ||
<FormattedMessage | ||
id="xpack.ingestPipelines.pipelineEditor.commonFields.keepOriginalFieldHelpText" | ||
defaultMessage="Copy the unparsed URI to {field}." | ||
values={{ | ||
field: <EuiCode>{'<target_field>.original'}</EuiCode>, | ||
}} | ||
/> | ||
), | ||
}, | ||
remove_if_successful: { | ||
type: FIELD_TYPES.TOGGLE, | ||
defaultValue: false, | ||
deserializer: to.booleanOrUndef, | ||
serializer: from.undefinedIfValue(false), | ||
label: i18n.translate( | ||
'xpack.ingestPipelines.pipelineEditor.commonFields.removeIfSuccessfulFieldLabel', | ||
{ | ||
defaultMessage: 'Remove if successful', | ||
} | ||
), | ||
helpText: ( | ||
<FormattedMessage | ||
id="xpack.ingestPipelines.pipelineEditor.commonFields.removeIfSuccessfulFieldHelpText" | ||
defaultMessage="Remove the field after parsing the URI string." | ||
/> | ||
), | ||
}, | ||
}; | ||
|
||
export const UriParts: FunctionComponent = () => { | ||
return ( | ||
<> | ||
<FieldNameField | ||
helpText={i18n.translate( | ||
'xpack.ingestPipelines.pipelineEditor.uriPartsForm.fieldNameHelpText', | ||
{ defaultMessage: 'Field containing URI string.' } | ||
)} | ||
/> | ||
|
||
<TargetField /> | ||
|
||
<UseField | ||
config={fieldsConfig.keep_original} | ||
component={ToggleField} | ||
path="fields.keep_original" | ||
data-test-subj="keepOriginalField" | ||
/> | ||
|
||
<UseField | ||
config={fieldsConfig.remove_if_successful} | ||
component={ToggleField} | ||
path="fields.remove_if_successful" | ||
data-test-subj="removeIfSuccessfulField" | ||
/> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters