[8.10][ESRE] Parse multiple fields when attaching (#161295)

## Summary

This PR adds support for parsing multiple field mappings from an
existing pipeline configuration. This is only relevant when listing
attachable pipelines; the new pipeline creation process is not affected.

This change is a pre-requisite for allowing ELSER pipelines to be
attached.

Tested for backward compatibility:
* Existing pipeline selection dropdown (temporarily disabled filtering
of ELSER pipelines)
![Screenshot 2023-07-05 at 15 53
52](00d673f7-70b4-4cbb-919c-4fe17eedaca2)

Note the "Source field" and "Destination field" attributes show the
first mapping's fields only (even if the pipeline is configured with
multiple mappings). This is pending redesign and will be addressed in a
later PR.

* Fields - ELSER pipeline (temporarily removed field modification
widgets)
![Screenshot 2023-07-05 at 15 54
34](8bdd49f9-1682-4ccb-a066-233e0809b883)

* Fields - non-ELSER pipeline
![Screenshot 2023-07-05 at 15 54
42](5c70eb9b-e9a9-4213-9d24-ac9303f64b54)

### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Adam Demjen 2023-07-06 14:57:10 -04:00 committed by GitHub
parent 7709670d92
commit 060cc5b029
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 18 deletions

View file

@ -381,12 +381,59 @@ describe('parseMlInferenceParametersFromPipeline', () => {
model_id: 'test-model',
pipeline_name: 'unit-test',
source_field: 'body',
field_mappings: [
{
sourceField: 'body',
targetField: 'ml.inference.test',
},
],
});
});
it('return null if pipeline missing inference processor', () => {
it('returns pipeline parameters from ingest pipeline with multiple inference processors', () => {
expect(
parseMlInferenceParametersFromPipeline('unit-test', {
processors: [
{
inference: {
field_map: {
body: 'text_field',
},
model_id: 'test-model',
target_field: 'ml.inference.body',
},
},
{
inference: {
field_map: {
title: 'text_field',
},
model_id: 'test-model',
target_field: 'ml.inference.title',
},
},
],
})
).toEqual({
destination_field: 'body',
model_id: 'test-model',
pipeline_name: 'unit-test',
source_field: 'body',
field_mappings: [
{
sourceField: 'body',
targetField: 'ml.inference.body',
},
{
sourceField: 'title',
targetField: 'ml.inference.title',
},
],
});
});
it('return null if pipeline is missing inference processor', () => {
expect(parseMlInferenceParametersFromPipeline('unit-test', { processors: [] })).toBeNull();
});
it('return null if pipeline missing field_map', () => {
it('return null if pipeline is missing field_map', () => {
expect(
parseMlInferenceParametersFromPipeline('unit-test', {
processors: [

View file

@ -216,24 +216,37 @@ export const parseMlInferenceParametersFromPipeline = (
name: string,
pipeline: IngestPipeline
): CreateMlInferencePipelineParameters | null => {
const processor = pipeline?.processors?.find((proc) => proc.inference !== undefined);
if (!processor || processor?.inference === undefined) {
const inferenceProcessors = pipeline?.processors
?.filter((p) => p.inference)
.map((p) => p.inference) as IngestInferenceProcessor[];
if (!inferenceProcessors || inferenceProcessors.length === 0) {
return null;
}
const { inference: inferenceProcessor } = processor;
const sourceFields = Object.keys(inferenceProcessor.field_map ?? {});
const sourceField = sourceFields.length === 1 ? sourceFields[0] : null;
if (!sourceField) {
return null;
}
return {
destination_field: inferenceProcessor.target_field
? stripMlInferencePrefix(inferenceProcessor.target_field)
: inferenceProcessor.target_field,
model_id: inferenceProcessor.model_id,
pipeline_name: name,
source_field: sourceField,
};
// Extract source -> target field mappings from all inference processors in pipeline
const fieldMappings = inferenceProcessors
.map((p) => {
const sourceFields = Object.keys(p.field_map ?? {});
// We assume that there is only one source field per inference processor
const sourceField = sourceFields.length >= 1 ? sourceFields[0] : null;
return {
sourceField,
targetField: p.target_field, // Prefixed target field
};
})
.filter((f) => f.sourceField) as FieldMapping[];
return fieldMappings.length === 0
? null
: {
destination_field: fieldMappings[0].targetField // Backward compatibility - TODO: remove after multi-field selector is implemented for all inference types
? stripMlInferencePrefix(fieldMappings[0].targetField)
: '',
model_id: inferenceProcessors[0].model_id,
pipeline_name: name,
source_field: fieldMappings[0].sourceField, // Backward compatibility - TODO: remove after multi-field selector is implemented for all inference types
field_mappings: fieldMappings,
};
};
export const parseModelStateFromStats = (

View file

@ -80,6 +80,7 @@ export interface CreateMlInferencePipelineParameters {
model_id: string;
pipeline_name: string;
source_field: string;
field_mappings?: FieldMapping[];
}
export interface CreateMLInferencePipelineDefinition {

View file

@ -326,6 +326,7 @@ export const MLInferenceLogic = kea<
modelID: params.model_id,
pipelineName,
sourceField: params.source_field,
fieldMappings: params.field_mappings,
});
},
setIndexName: ({ indexName }) => {