diff --git a/x-pack/plugins/streams/common/types.ts b/x-pack/plugins/streams/common/types.ts index 3d8e0fc0d390c..03765f3cf990c 100644 --- a/x-pack/plugins/streams/common/types.ts +++ b/x-pack/plugins/streams/common/types.ts @@ -72,7 +72,8 @@ export type ProcessingDefinition = z.infer; export const fieldDefinitionSchema = z.object({ name: z.string(), - type: z.enum(['keyword', 'match_only_text', 'long', 'double', 'date', 'boolean', 'ip']), + type: z.enum(['keyword', 'match_only_text', 'long', 'double', 'date', 'boolean', 'ip', 'alias']), + alias_path: z.optional(z.string()), }); export type FieldDefinition = z.infer; diff --git a/x-pack/plugins/streams/server/lib/streams/component_templates/generate_layer.ts b/x-pack/plugins/streams/server/lib/streams/component_templates/generate_layer.ts index 69da4c5d8287d..48a8f977187d3 100644 --- a/x-pack/plugins/streams/server/lib/streams/component_templates/generate_layer.ts +++ b/x-pack/plugins/streams/server/lib/streams/component_templates/generate_layer.ts @@ -8,6 +8,7 @@ import { ClusterPutComponentTemplateRequest, MappingDateProperty, + MappingFieldAliasProperty, MappingProperty, } from '@elastic/elasticsearch/lib/api/types'; import { StreamDefinition } from '../../../../common/types'; @@ -18,7 +19,8 @@ import { getComponentTemplateName } from './name'; export function generateLayer( id: string, - definition: StreamDefinition + definition: StreamDefinition, + rootDefinition: StreamDefinition | undefined ): ClusterPutComponentTemplateRequest { const properties: Record = {}; definition.fields.forEach((field) => { @@ -29,6 +31,18 @@ export function generateLayer( // @timestamp can't ignore malformed dates as it's used for sorting in logsdb (property as MappingDateProperty).ignore_malformed = false; } + if (field.type === 'alias' && field.alias_path) { + (property as MappingFieldAliasProperty).path = field.alias_path; + // try to find the referenced field in the root definition and add it here as well to satisfy the Elasticsearch alias checker + if (rootDefinition) { + const rootField = rootDefinition.fields.find((f) => f.name === field.alias_path); + if (rootField) { + properties[field.alias_path] = { + type: rootField.type, + }; + } + } + } properties[field.name] = property; }); return { diff --git a/x-pack/plugins/streams/server/lib/streams/root_stream_definition.ts b/x-pack/plugins/streams/server/lib/streams/root_stream_definition.ts index 1bdb4f20a95cc..026cadaaf031c 100644 --- a/x-pack/plugins/streams/server/lib/streams/root_stream_definition.ts +++ b/x-pack/plugins/streams/server/lib/streams/root_stream_definition.ts @@ -21,10 +21,18 @@ export const rootStreamDefinition: StreamDefinition = { name: 'message', type: 'match_only_text', }, + { + name: 'body.text', + type: 'match_only_text', + }, { name: 'host.name', type: 'keyword', }, + { + name: 'resource.attributes.host.name', + type: 'keyword', + }, { name: 'log.level', type: 'keyword', diff --git a/x-pack/plugins/streams/server/lib/streams/stream_crud.ts b/x-pack/plugins/streams/server/lib/streams/stream_crud.ts index a0c9c2660b3e3..926ef9031bc68 100644 --- a/x-pack/plugins/streams/server/lib/streams/stream_crud.ts +++ b/x-pack/plugins/streams/server/lib/streams/stream_crud.ts @@ -369,6 +369,10 @@ export async function validateAncestorFields( }); for (const ancestor of ancestors) { for (const field of fields) { + if (field.type === 'alias') { + // TODO: validate whether field under alias_path has the same type as an ancestor field definition of the current field + continue; + } if ( ancestor.definition.fields.some( (ancestorField) => ancestorField.type !== field.type && ancestorField.name === field.name @@ -455,7 +459,7 @@ export async function syncStream({ }); return; } - const componentTemplate = generateLayer(definition.id, definition); + const componentTemplate = generateLayer(definition.id, definition, rootDefinition); await upsertComponent({ esClient: scopedClusterClient.asCurrentUser, logger,