[8.x] [Streams 🌊] Enrichment - Add support for date processor (#213559) (#215359)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Streams 🌊] Enrichment - Add support for date processor
(#213559)](https://github.com/elastic/kibana/pull/213559)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Marco Antonio
Ghiani","email":"marcoantonio.ghiani01@gmail.com"},"sourceCommit":{"committedDate":"2025-03-20T12:57:47Z","message":"[Streams
🌊] Enrichment - Add support for date processor (#213559)\n\n## 📓
Summary\n\nPart of
https://github.com/elastic/streams-program/issues/38\n\nThis work adds
the `date` processor along with the dissect and grok ones\nin the
enrichment section.\nIt scales well following the current folder
structure, but we should\ndefinitely polish it a bit more once more
processors get added, such as\ngetting the right form component, improve
form state derivation,
etc.\n\n\nhttps://github.com/user-attachments/assets/824d15c8-ce9d-455a-ae0b-97aeec8cf025\n\n---------\n\nCo-authored-by:
Kerry Gallagher <kerry.gallagher@elastic.co>\nCo-authored-by: Joe Reuter
<johannes.reuter@elastic.co>\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Kerry
Gallagher
<471693+Kerry350@users.noreply.github.com>","sha":"8f65dceefca69c70de33be8176942d0c89fb0e27","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:obs-ux-logs","backport:version","Feature:Streams","v9.1.0","v8.19.0"],"title":"[Streams
🌊] Enrichment - Add support for date
processor","number":213559,"url":"https://github.com/elastic/kibana/pull/213559","mergeCommit":{"message":"[Streams
🌊] Enrichment - Add support for date processor (#213559)\n\n## 📓
Summary\n\nPart of
https://github.com/elastic/streams-program/issues/38\n\nThis work adds
the `date` processor along with the dissect and grok ones\nin the
enrichment section.\nIt scales well following the current folder
structure, but we should\ndefinitely polish it a bit more once more
processors get added, such as\ngetting the right form component, improve
form state derivation,
etc.\n\n\nhttps://github.com/user-attachments/assets/824d15c8-ce9d-455a-ae0b-97aeec8cf025\n\n---------\n\nCo-authored-by:
Kerry Gallagher <kerry.gallagher@elastic.co>\nCo-authored-by: Joe Reuter
<johannes.reuter@elastic.co>\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Kerry
Gallagher
<471693+Kerry350@users.noreply.github.com>","sha":"8f65dceefca69c70de33be8176942d0c89fb0e27"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/213559","number":213559,"mergeCommit":{"message":"[Streams
🌊] Enrichment - Add support for date processor (#213559)\n\n## 📓
Summary\n\nPart of
https://github.com/elastic/streams-program/issues/38\n\nThis work adds
the `date` processor along with the dissect and grok ones\nin the
enrichment section.\nIt scales well following the current folder
structure, but we should\ndefinitely polish it a bit more once more
processors get added, such as\ngetting the right form component, improve
form state derivation,
etc.\n\n\nhttps://github.com/user-attachments/assets/824d15c8-ce9d-455a-ae0b-97aeec8cf025\n\n---------\n\nCo-authored-by:
Kerry Gallagher <kerry.gallagher@elastic.co>\nCo-authored-by: Joe Reuter
<johannes.reuter@elastic.co>\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Kerry
Gallagher
<471693+Kerry350@users.noreply.github.com>","sha":"8f65dceefca69c70de33be8176942d0c89fb0e27"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

---------

Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani01@gmail.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Kerry Gallagher 2025-03-20 18:24:06 +00:00 committed by GitHub
parent 0a7f9f74dc
commit bbabd6d344
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 2039 additions and 516 deletions

View file

@ -44484,7 +44484,7 @@
{
"additionalProperties": false,
"properties": {
"grok": {
"date": {
"allOf": [
{
"properties": {
@ -44631,27 +44631,33 @@
"minLength": 1,
"type": "string"
},
"ignore_missing": {
"type": "boolean"
},
"pattern_definitions": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"patterns": {
"formats": {
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array"
},
"locale": {
"minLength": 1,
"type": "string"
},
"output_format": {
"minLength": 1,
"type": "string"
},
"target_field": {
"minLength": 1,
"type": "string"
},
"timezone": {
"minLength": 1,
"type": "string"
}
},
"required": [
"field",
"patterns"
"formats"
],
"type": "object"
}
@ -44659,7 +44665,7 @@
}
},
"required": [
"grok"
"date"
],
"type": "object"
},
@ -44838,6 +44844,188 @@
"dissect"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"grok": {
"allOf": [
{
"properties": {
"description": {
"type": "string"
},
"if": {
"anyOf": [
{
"anyOf": [
{
"additionalProperties": false,
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"operator": {
"enum": [
"exists",
"notExists"
],
"type": "string"
}
},
"required": [
"field",
"operator"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"operator": {
"enum": [
"eq",
"neq",
"lt",
"lte",
"gt",
"gte",
"contains",
"startsWith",
"endsWith"
],
"type": "string"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
}
},
"required": [
"field",
"operator",
"value"
],
"type": "object"
}
]
},
{
"additionalProperties": false,
"properties": {
"and": {
"items": {},
"type": "array"
}
},
"required": [
"and"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"or": {
"items": {},
"type": "array"
}
},
"required": [
"or"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"never": {
"additionalProperties": false,
"properties": {},
"type": "object"
}
},
"required": [
"never"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"always": {
"additionalProperties": false,
"properties": {},
"type": "object"
}
},
"required": [
"always"
],
"type": "object"
}
]
},
"ignore_failure": {
"type": "boolean"
}
},
"required": [
"if"
],
"type": "object"
},
{
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"ignore_missing": {
"type": "boolean"
},
"pattern_definitions": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"patterns": {
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array"
}
},
"required": [
"field",
"patterns"
],
"type": "object"
}
]
}
},
"required": [
"grok"
],
"type": "object"
}
]
},
@ -45223,7 +45411,7 @@
{
"additionalProperties": false,
"properties": {
"grok": {
"date": {
"allOf": [
{
"properties": {
@ -45370,27 +45558,33 @@
"minLength": 1,
"type": "string"
},
"ignore_missing": {
"type": "boolean"
},
"pattern_definitions": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"patterns": {
"formats": {
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array"
},
"locale": {
"minLength": 1,
"type": "string"
},
"output_format": {
"minLength": 1,
"type": "string"
},
"target_field": {
"minLength": 1,
"type": "string"
},
"timezone": {
"minLength": 1,
"type": "string"
}
},
"required": [
"field",
"patterns"
"formats"
],
"type": "object"
}
@ -45398,7 +45592,7 @@
}
},
"required": [
"grok"
"date"
],
"type": "object"
},
@ -45577,6 +45771,188 @@
"dissect"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"grok": {
"allOf": [
{
"properties": {
"description": {
"type": "string"
},
"if": {
"anyOf": [
{
"anyOf": [
{
"additionalProperties": false,
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"operator": {
"enum": [
"exists",
"notExists"
],
"type": "string"
}
},
"required": [
"field",
"operator"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"operator": {
"enum": [
"eq",
"neq",
"lt",
"lte",
"gt",
"gte",
"contains",
"startsWith",
"endsWith"
],
"type": "string"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
}
},
"required": [
"field",
"operator",
"value"
],
"type": "object"
}
]
},
{
"additionalProperties": false,
"properties": {
"and": {
"items": {},
"type": "array"
}
},
"required": [
"and"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"or": {
"items": {},
"type": "array"
}
},
"required": [
"or"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"never": {
"additionalProperties": false,
"properties": {},
"type": "object"
}
},
"required": [
"never"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"always": {
"additionalProperties": false,
"properties": {},
"type": "object"
}
},
"required": [
"always"
],
"type": "object"
}
]
},
"ignore_failure": {
"type": "boolean"
}
},
"required": [
"if"
],
"type": "object"
},
{
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"ignore_missing": {
"type": "boolean"
},
"pattern_definitions": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"patterns": {
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array"
}
},
"required": [
"field",
"patterns"
],
"type": "object"
}
]
}
},
"required": [
"grok"
],
"type": "object"
}
]
},
@ -46128,7 +46504,7 @@
{
"additionalProperties": false,
"properties": {
"grok": {
"date": {
"allOf": [
{
"properties": {
@ -46275,27 +46651,33 @@
"minLength": 1,
"type": "string"
},
"ignore_missing": {
"type": "boolean"
},
"pattern_definitions": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"patterns": {
"formats": {
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array"
},
"locale": {
"minLength": 1,
"type": "string"
},
"output_format": {
"minLength": 1,
"type": "string"
},
"target_field": {
"minLength": 1,
"type": "string"
},
"timezone": {
"minLength": 1,
"type": "string"
}
},
"required": [
"field",
"patterns"
"formats"
],
"type": "object"
}
@ -46303,7 +46685,7 @@
}
},
"required": [
"grok"
"date"
],
"type": "object"
},
@ -46482,6 +46864,188 @@
"dissect"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"grok": {
"allOf": [
{
"properties": {
"description": {
"type": "string"
},
"if": {
"anyOf": [
{
"anyOf": [
{
"additionalProperties": false,
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"operator": {
"enum": [
"exists",
"notExists"
],
"type": "string"
}
},
"required": [
"field",
"operator"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"operator": {
"enum": [
"eq",
"neq",
"lt",
"lte",
"gt",
"gte",
"contains",
"startsWith",
"endsWith"
],
"type": "string"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
}
},
"required": [
"field",
"operator",
"value"
],
"type": "object"
}
]
},
{
"additionalProperties": false,
"properties": {
"and": {
"items": {},
"type": "array"
}
},
"required": [
"and"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"or": {
"items": {},
"type": "array"
}
},
"required": [
"or"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"never": {
"additionalProperties": false,
"properties": {},
"type": "object"
}
},
"required": [
"never"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"always": {
"additionalProperties": false,
"properties": {},
"type": "object"
}
},
"required": [
"always"
],
"type": "object"
}
]
},
"ignore_failure": {
"type": "boolean"
}
},
"required": [
"if"
],
"type": "object"
},
{
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"ignore_missing": {
"type": "boolean"
},
"pattern_definitions": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"patterns": {
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array"
}
},
"required": [
"field",
"patterns"
],
"type": "object"
}
]
}
},
"required": [
"grok"
],
"type": "object"
}
]
},
@ -46840,7 +47404,7 @@
{
"additionalProperties": false,
"properties": {
"grok": {
"date": {
"allOf": [
{
"properties": {
@ -46987,27 +47551,33 @@
"minLength": 1,
"type": "string"
},
"ignore_missing": {
"type": "boolean"
},
"pattern_definitions": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"patterns": {
"formats": {
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array"
},
"locale": {
"minLength": 1,
"type": "string"
},
"output_format": {
"minLength": 1,
"type": "string"
},
"target_field": {
"minLength": 1,
"type": "string"
},
"timezone": {
"minLength": 1,
"type": "string"
}
},
"required": [
"field",
"patterns"
"formats"
],
"type": "object"
}
@ -47015,7 +47585,7 @@
}
},
"required": [
"grok"
"date"
],
"type": "object"
},
@ -47194,6 +47764,188 @@
"dissect"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"grok": {
"allOf": [
{
"properties": {
"description": {
"type": "string"
},
"if": {
"anyOf": [
{
"anyOf": [
{
"additionalProperties": false,
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"operator": {
"enum": [
"exists",
"notExists"
],
"type": "string"
}
},
"required": [
"field",
"operator"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"operator": {
"enum": [
"eq",
"neq",
"lt",
"lte",
"gt",
"gte",
"contains",
"startsWith",
"endsWith"
],
"type": "string"
},
"value": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
}
},
"required": [
"field",
"operator",
"value"
],
"type": "object"
}
]
},
{
"additionalProperties": false,
"properties": {
"and": {
"items": {},
"type": "array"
}
},
"required": [
"and"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"or": {
"items": {},
"type": "array"
}
},
"required": [
"or"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"never": {
"additionalProperties": false,
"properties": {},
"type": "object"
}
},
"required": [
"never"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"always": {
"additionalProperties": false,
"properties": {},
"type": "object"
}
},
"required": [
"always"
],
"type": "object"
}
]
},
"ignore_failure": {
"type": "boolean"
}
},
"required": [
"if"
],
"type": "object"
},
{
"properties": {
"field": {
"minLength": 1,
"type": "string"
},
"ignore_missing": {
"type": "boolean"
},
"pattern_definitions": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"patterns": {
"items": {
"minLength": 1,
"type": "string"
},
"minItems": 1,
"type": "array"
}
},
"required": [
"field",
"patterns"
],
"type": "object"
}
]
}
},
"required": [
"grok"
],
"type": "object"
}
]
},

File diff suppressed because it is too large Load diff

View file

@ -68,7 +68,37 @@ export const dissectProcessorDefinitionSchema = z.strictObject({
),
}) satisfies z.Schema<DissectProcessorDefinition>;
export type ProcessorDefinition = DissectProcessorDefinition | GrokProcessorDefinition;
export interface DateProcessorConfig extends ProcessorBase {
field: string;
formats: string[];
locale?: string;
target_field?: string;
timezone?: string;
output_format?: string;
}
export interface DateProcessorDefinition {
date: DateProcessorConfig;
}
export const dateProcessorDefinitionSchema = z.strictObject({
date: z.intersection(
processorBaseSchema,
z.object({
field: NonEmptyString,
formats: z.array(NonEmptyString),
locale: z.optional(NonEmptyString),
target_field: z.optional(NonEmptyString),
timezone: z.optional(NonEmptyString),
output_format: z.optional(NonEmptyString),
})
),
}) satisfies z.Schema<DateProcessorDefinition>;
export type ProcessorDefinition =
| DateProcessorDefinition
| DissectProcessorDefinition
| GrokProcessorDefinition;
export type ProcessorDefinitionWithId = ProcessorDefinition & { id: string };
type UnionKeysOf<T extends Record<string, any>> = T extends T ? keyof T : never;
@ -82,13 +112,15 @@ export type ProcessorTypeOf<TProcessorDefinition extends ProcessorDefinition> =
UnionKeysOf<TProcessorDefinition> & ProcessorType;
export const processorDefinitionSchema: z.ZodType<ProcessorDefinition> = z.union([
grokProcessorDefinitionSchema,
dateProcessorDefinitionSchema,
dissectProcessorDefinitionSchema,
grokProcessorDefinitionSchema,
]);
export const processorWithIdDefinitionSchema: z.ZodType<ProcessorDefinitionWithId> = z.union([
grokProcessorDefinitionSchema.merge(z.object({ id: z.string() })),
dateProcessorDefinitionSchema.merge(z.object({ id: z.string() })),
dissectProcessorDefinitionSchema.merge(z.object({ id: z.string() })),
grokProcessorDefinitionSchema.merge(z.object({ id: z.string() })),
]);
export const isGrokProcessorDefinition = createIsNarrowSchema(
@ -101,6 +133,11 @@ export const isDissectProcessorDefinition = createIsNarrowSchema(
dissectProcessorDefinitionSchema
);
export const isDateProcessorDefinition = createIsNarrowSchema(
processorDefinitionSchema,
dateProcessorDefinitionSchema
);
const processorTypes: ProcessorType[] = (processorDefinitionSchema as z.ZodUnion<any>).options.map(
(option: z.ZodUnion<any>['options'][number]) => Object.keys(option.shape)[0]
);

View file

@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { useController } from 'react-hook-form';
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { ProcessorFormState } from '../../types';
export const DateFormatsField = () => {
const { field, fieldState } = useController<ProcessorFormState, 'formats'>({
name: 'formats',
rules: {
required: i18n.translate(
'xpack.streams.streamDetailView.managementTab.enrichment.processor.dateFormatsRequiredError',
{ defaultMessage: 'A value for format is required.' }
),
},
});
const { invalid, error } = fieldState;
const { ref, ...inputProps } = field;
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
field.onChange([e.target.value]);
};
return (
<EuiFormRow
label={i18n.translate(
'xpack.streams.streamDetailView.managementTab.enrichment.processor.dateFormatsLabel',
{ defaultMessage: 'Format' }
)}
helpText={
<FormattedMessage
id="xpack.streams.streamDetailView.managementTab.enrichment.processor.dateFormatsHelpText"
defaultMessage="Expected date format. Accepts a Java time pattern, ISO8601, UNIX, UNIX_MS, or TAI64N format."
/>
}
fullWidth
isInvalid={invalid}
error={error?.message}
>
<EuiFieldText {...inputProps} inputRef={ref} isInvalid={invalid} onChange={handleChange} />
</EuiFormRow>
);
};

View file

@ -0,0 +1,111 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { useFormContext } from 'react-hook-form';
import { EuiCode, EuiFieldText, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
export const DateTargetField = () => {
const { register } = useFormContext();
const { ref, ...inputProps } = register('target_field');
return (
<EuiFormRow
label={i18n.translate(
'xpack.streams.streamDetailView.managementTab.enrichment.processor.dateTargetLabel',
{ defaultMessage: 'Target field' }
)}
helpText={
<FormattedMessage
id="xpack.streams.streamDetailView.managementTab.enrichment.processor.dateTargetHelpText"
defaultMessage="The field that will hold the parsed date. Defaults to {target}."
values={{ target: <EuiCode>@timestamp</EuiCode> }}
/>
}
fullWidth
>
<EuiFieldText {...inputProps} inputRef={ref} />
</EuiFormRow>
);
};
export const DateTimezoneField = () => {
const { register } = useFormContext();
const { ref, ...inputProps } = register('timezone');
return (
<EuiFormRow
label={i18n.translate(
'xpack.streams.streamDetailView.managementTab.enrichment.processor.dateTimezoneLabel',
{ defaultMessage: 'Timezone' }
)}
helpText={
<FormattedMessage
id="xpack.streams.streamDetailView.managementTab.enrichment.processor.dateTimezoneHelpText"
defaultMessage="The timezone to use when parsing the date. Supports template snippets. Defaults to {timezone}"
values={{ timezone: <EuiCode>UTC</EuiCode> }}
/>
}
fullWidth
>
<EuiFieldText {...inputProps} inputRef={ref} />
</EuiFormRow>
);
};
export const DateLocaleField = () => {
const { register } = useFormContext();
const { ref, ...inputProps } = register('locale');
return (
<EuiFormRow
label={i18n.translate(
'xpack.streams.streamDetailView.managementTab.enrichment.processor.dateLocaleLabel',
{ defaultMessage: 'Locale' }
)}
helpText={
<FormattedMessage
id="xpack.streams.streamDetailView.managementTab.enrichment.processor.dateLocaleHelpText"
defaultMessage="The locale to use when parsing the date, relevant when parsing month names or week days. Supports template snippets. Defaults to {locale}."
values={{ locale: <EuiCode>ENGLISH</EuiCode> }}
/>
}
fullWidth
>
<EuiFieldText {...inputProps} inputRef={ref} />
</EuiFormRow>
);
};
export const DateOutputFormatField = () => {
const { register } = useFormContext();
const { ref, ...inputProps } = register('output_format');
return (
<EuiFormRow
label={i18n.translate(
'xpack.streams.streamDetailView.managementTab.enrichment.processor.dateOutputFormatLabel',
{ defaultMessage: 'Output format' }
)}
helpText={
<FormattedMessage
id="xpack.streams.streamDetailView.managementTab.enrichment.processor.dateOutputFormatHelpText"
defaultMessage="The format to use when writing the date to {field}. Must be a valid java time pattern. Defaults to {outputFormat}."
values={{
field: <EuiCode>target_field</EuiCode>,
outputFormat: <EuiCode>yyyy-MM-dd&apos;T&apos;HH:mm:ss.SSSXXX</EuiCode>,
}}
/>
}
fullWidth
>
<EuiFieldText {...inputProps} inputRef={ref} />
</EuiFormRow>
);
};

View file

@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { EuiSpacer } from '@elastic/eui';
import { ProcessorFieldSelector } from '../processor_field_selector';
import { OptionalFieldsAccordion } from '../optional_fields_accordion';
import { ProcessorConditionEditor } from '../processor_condition_editor';
import { IgnoreFailureToggle } from '../ignore_toggles';
import {
DateTargetField,
DateTimezoneField,
DateOutputFormatField,
DateLocaleField,
} from './date_optional_fields';
import { DateFormatsField } from './date_formats_field';
export const DateProcessorForm = () => {
return (
<>
<ProcessorFieldSelector />
<DateFormatsField />
<EuiSpacer size="m" />
<OptionalFieldsAccordion>
<DateTargetField />
<DateTimezoneField />
<DateLocaleField />
<DateOutputFormatField />
<EuiSpacer size="m" />
<ProcessorConditionEditor />
</OptionalFieldsAccordion>
<EuiSpacer size="m" />
<IgnoreFailureToggle />
</>
);
};

View file

@ -37,6 +37,7 @@ import {
isGrokProcessor,
isDissectProcessor,
getDefaultFormStateByType,
isDateProcessor,
} from '../utils';
import { ProcessorErrors, ProcessorMetricBadges } from './processor_metrics';
import {
@ -46,6 +47,7 @@ import {
StreamEnrichmentContext,
} from '../state_management/stream_enrichment_state_machine';
import { ProcessorMetrics } from '../state_management/simulation_state_machine';
import { DateProcessorForm } from './date';
export function AddProcessorPanel() {
const { euiTheme } = useEuiTheme();
@ -166,8 +168,9 @@ export function AddProcessorPanel() {
<EuiForm component="form" fullWidth onSubmit={methods.handleSubmit(handleSubmit)}>
<ProcessorTypeSelector />
<EuiSpacer size="m" />
{type === 'grok' && <GrokProcessorForm />}
{type === 'date' && <DateProcessorForm />}
{type === 'dissect' && <DissectProcessorForm />}
{type === 'grok' && <GrokProcessorForm />}
</EuiForm>
{processorMetrics && !isEmpty(processorMetrics.errors) && (
<ProcessorErrors metrics={processorMetrics} />
@ -351,6 +354,7 @@ export function EditProcessorPanel({ processorRef, processorMetrics }: EditProce
<EuiForm component="form" fullWidth onSubmit={methods.handleSubmit(handleSubmit)}>
<ProcessorTypeSelector disabled />
<EuiSpacer size="m" />
{type === 'date' && <DateProcessorForm />}
{type === 'grok' && <GrokProcessorForm />}
{type === 'dissect' && <DissectProcessorForm />}
</EuiForm>
@ -392,6 +396,8 @@ const getProcessorDescription = (processor: ProcessorDefinitionWithUIAttributes)
return processor.grok.patterns.join(' • ');
} else if (isDissectProcessor(processor)) {
return processor.dissect.pattern;
} else if (isDateProcessor(processor)) {
return `${processor.date.field}${processor.date.formats.join(' - ')}`;
}
return '';

View file

@ -68,6 +68,16 @@ export const ProcessorTypeSelector = ({
};
const availableProcessors: TAvailableProcessors = {
date: {
value: 'date',
inputDisplay: 'Date',
getDocUrl: () => (
<FormattedMessage
id="xpack.streams.streamDetailView.managementTab.enrichment.processor.dateHelpText"
defaultMessage="Converts a date to a document timestamp."
/>
),
},
dissect: {
value: 'dissect',
inputDisplay: 'Dissect',

View file

@ -11,9 +11,11 @@ import { EuiFormRow, EuiFormRowProps, EuiSwitch, htmlIdGenerator } from '@elasti
import { ProcessorFormState } from '../types';
type ExtractBooleanFields<TInput> = NonNullable<
{
[K in keyof TInput]: boolean extends TInput[K] ? K : never;
}[keyof TInput]
TInput extends Record<string, unknown>
? {
[K in keyof TInput]: boolean extends TInput[K] ? K : never;
}[keyof TInput]
: never
>;
interface ToggleFieldProps {

View file

@ -6,6 +6,7 @@
*/
import {
DateProcessorConfig,
DissectProcessorConfig,
FieldDefinitionType,
GrokProcessorConfig,
@ -25,16 +26,13 @@ export interface DetectedField {
type?: FieldDefinitionType | 'system';
}
interface BaseFormState {
detected_fields?: DetectedField[];
}
export type GrokFormState = Omit<GrokProcessorConfig, 'patterns'> & {
type: 'grok';
patterns: Array<{ value: string }>;
};
export type GrokFormState = BaseFormState &
Omit<GrokProcessorConfig, 'patterns'> & {
type: 'grok';
patterns: Array<{ value: string }>;
};
export type DissectFormState = DissectProcessorConfig & { type: 'dissect' };
export type DissectFormState = BaseFormState & DissectProcessorConfig & { type: 'dissect' };
export type DateFormState = DateProcessorConfig & { type: 'date' };
export type ProcessorFormState = GrokFormState | DissectFormState;
export type ProcessorFormState = GrokFormState | DissectFormState | DateFormState;

View file

@ -21,16 +21,19 @@ import {
GrokFormState,
ProcessorFormState,
WithUIAttributes,
DateFormState,
} from './types';
import { ALWAYS_CONDITION } from '../../../util/condition';
const defaultGrokProcessorFormState: GrokFormState = {
type: 'grok',
field: 'message',
patterns: [{ value: '' }],
pattern_definitions: {},
const defaultDateProcessorFormState: DateFormState = {
type: 'date',
field: '',
formats: [],
locale: '',
target_field: '',
timezone: '',
output_format: '',
ignore_failure: true,
ignore_missing: true,
if: ALWAYS_CONDITION,
};
@ -43,7 +46,18 @@ const defaultDissectProcessorFormState: DissectFormState = {
if: ALWAYS_CONDITION,
};
const defaultGrokProcessorFormState: GrokFormState = {
type: 'grok',
field: 'message',
patterns: [{ value: '' }],
pattern_definitions: {},
ignore_failure: true,
ignore_missing: true,
if: ALWAYS_CONDITION,
};
const defaultProcessorFormStateByType: Record<ProcessorType, ProcessorFormState> = {
date: defaultDateProcessorFormState,
dissect: defaultDissectProcessorFormState,
grok: defaultGrokProcessorFormState,
};
@ -75,6 +89,15 @@ export const getFormStateFrom = (
});
}
if (isDateProcessor(processor)) {
const { date } = processor;
return structuredClone({
...date,
type: 'date',
});
}
throw new Error(`Form state for processor type "${processor.type}" is not implemented.`);
};
@ -109,6 +132,24 @@ export const convertFormStateToProcessor = (formState: ProcessorFormState): Proc
};
}
if (formState.type === 'date') {
const { field, formats, locale, ignore_failure, target_field, timezone, output_format } =
formState;
return {
date: {
if: formState.if,
field,
formats,
ignore_failure,
locale: isEmpty(locale) ? undefined : locale,
target_field: isEmpty(target_field) ? undefined : target_field,
timezone: isEmpty(timezone) ? undefined : timezone,
output_format: isEmpty(output_format) ? undefined : output_format,
},
};
}
throw new Error('Cannot convert form state to processing: unknown type.');
};
@ -121,8 +162,9 @@ const createProcessorGuardByType =
> =>
processor.type === type;
export const isGrokProcessor = createProcessorGuardByType('grok');
export const isDateProcessor = createProcessorGuardByType('date');
export const isDissectProcessor = createProcessorGuardByType('dissect');
export const isGrokProcessor = createProcessorGuardByType('grok');
const createId = htmlIdGenerator();
const toUIDefinition = <TProcessorDefinition extends ProcessorDefinition>(