mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[8.10] [Enterprise Search] Make single target field editable in multi-field editor (#162512)
## Summary This PR enhances the multi-field configuration screen for creating an ML inference pipeline. It adds different behavior to the target field based on the selected model: - For ELSER pipelines the target field is not editable, and the output field names are automatically generated as `ml.inference.<source field>_expanded` - For non-ELSER pipelines the target field is editable if there's a single source field selected. For multiple source fields the names are automatically generated as `ml.inference.<source field>` The mapping auto-updater process now receives `model_id` and only changes the mapping for ELSER pipelines. In order to keep the scope of changes smaller, we're NOT switching over to the multi-field selector for non-ELSER pipelines just yet. We're making the logic ready for this here, but the actual switchover will happen in a following PR. Non-ELSER pipelines  ELSER pipelines  ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [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 - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
11e56ec4c4
commit
c82b679c24
17 changed files with 250 additions and 38 deletions
|
@ -86,6 +86,7 @@ export interface CreateMlInferencePipelineParameters {
|
|||
export interface CreateMLInferencePipelineDefinition {
|
||||
field_mappings: FieldMapping[];
|
||||
inference_config?: InferencePipelineInferenceConfig;
|
||||
model_id: string;
|
||||
pipeline_definition: MlInferencePipeline;
|
||||
pipeline_name: string;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ describe('CreateMlInferencePipelineApiLogic', () => {
|
|||
},
|
||||
],
|
||||
indexName: 'my-index',
|
||||
modelId: 'my-model-id',
|
||||
pipelineName: 'my-pipeline',
|
||||
pipelineDefinition: { processors: [], version: 1 },
|
||||
};
|
||||
|
@ -39,7 +40,7 @@ describe('CreateMlInferencePipelineApiLogic', () => {
|
|||
expect(http.post).toHaveBeenCalledWith(
|
||||
'/internal/enterprise_search/indices/my-index/ml_inference/pipeline_processors',
|
||||
{
|
||||
body: '{"field_mappings":[{"sourceField":"my_source_field","targetField":"my_target_field"}],"pipeline_definition":{"processors":[],"version":1},"pipeline_name":"my-pipeline"}',
|
||||
body: '{"field_mappings":[{"sourceField":"my_source_field","targetField":"my_target_field"}],"model_id":"my-model-id","pipeline_definition":{"processors":[],"version":1},"pipeline_name":"my-pipeline"}',
|
||||
}
|
||||
);
|
||||
expect(result).toEqual({
|
||||
|
|
|
@ -18,6 +18,7 @@ export interface CreateMlInferencePipelineApiLogicArgs {
|
|||
fieldMappings: FieldMapping[];
|
||||
indexName: string;
|
||||
inferenceConfig?: InferencePipelineInferenceConfig;
|
||||
modelId: string;
|
||||
pipelineDefinition: MlInferencePipeline;
|
||||
pipelineName: string;
|
||||
}
|
||||
|
@ -33,6 +34,7 @@ export const createMlInferencePipeline = async (
|
|||
const params: CreateMLInferencePipelineDefinition = {
|
||||
field_mappings: args.fieldMappings,
|
||||
inference_config: args.inferenceConfig,
|
||||
model_id: args.modelId,
|
||||
pipeline_definition: args.pipelineDefinition,
|
||||
pipeline_name: args.pipelineName,
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('ConfigureFields', () => {
|
|||
addInferencePipelineModal: { configuration: { existingPipeline: false } },
|
||||
};
|
||||
|
||||
it('renders multi-field selector components if non-text expansion model is selected', () => {
|
||||
it('renders single field selector component if non-text expansion model is selected', () => {
|
||||
setMockValues(mockValues);
|
||||
const wrapper = shallow(<ConfigureFields />);
|
||||
expect(wrapper.find(SingleFieldMapping)).toHaveLength(1);
|
||||
|
|
|
@ -172,6 +172,7 @@ describe('MlInferenceLogic', () => {
|
|||
},
|
||||
],
|
||||
indexName: 'test',
|
||||
modelId: 'test-model',
|
||||
pipelineDefinition: {},
|
||||
pipelineName: 'unit-test',
|
||||
});
|
||||
|
@ -340,6 +341,7 @@ describe('MlInferenceLogic', () => {
|
|||
pipelineName: 'unit-test',
|
||||
sourceField: '',
|
||||
fieldMappings: [],
|
||||
targetField: '',
|
||||
});
|
||||
|
||||
expect(MLInferenceLogic.values.mlInferencePipeline).toBeUndefined();
|
||||
|
@ -352,6 +354,7 @@ describe('MlInferenceLogic', () => {
|
|||
pipelineName: 'unit-test',
|
||||
sourceField: 'body',
|
||||
fieldMappings: [],
|
||||
targetField: '',
|
||||
});
|
||||
|
||||
expect(MLInferenceLogic.values.mlInferencePipeline).not.toBeUndefined();
|
||||
|
@ -364,6 +367,7 @@ describe('MlInferenceLogic', () => {
|
|||
pipelineName: '',
|
||||
sourceField: '',
|
||||
fieldMappings: [],
|
||||
targetField: '',
|
||||
});
|
||||
expect(MLInferenceLogic.values.mlInferencePipeline).toBeUndefined();
|
||||
});
|
||||
|
@ -383,6 +387,7 @@ describe('MlInferenceLogic', () => {
|
|||
pipelineName: 'unit-test',
|
||||
sourceField: '',
|
||||
fieldMappings: [],
|
||||
targetField: '',
|
||||
});
|
||||
expect(MLInferenceLogic.values.mlInferencePipeline).not.toBeUndefined();
|
||||
expect(MLInferenceLogic.values.mlInferencePipeline).toEqual(existingPipeline);
|
||||
|
@ -563,12 +568,13 @@ describe('MlInferenceLogic', () => {
|
|||
|
||||
MLModelsApiLogic.actions.apiSuccess([textExpansionModel]);
|
||||
MLInferenceLogic.actions.selectFields(['my_source_field1', 'my_source_field2']);
|
||||
MLInferenceLogic.actions.addSelectedFieldsToMapping();
|
||||
MLInferenceLogic.actions.addSelectedFieldsToMapping(true);
|
||||
MLInferenceLogic.actions.createPipeline();
|
||||
|
||||
expect(MLInferenceLogic.actions.makeCreatePipelineRequest).toHaveBeenCalledWith({
|
||||
indexName: mockModelConfiguration.indexName,
|
||||
inferenceConfig: undefined,
|
||||
modelId: textExpansionModel.model_id,
|
||||
fieldMappings: [
|
||||
{
|
||||
sourceField: 'my_source_field1',
|
||||
|
@ -609,6 +615,7 @@ describe('MlInferenceLogic', () => {
|
|||
targetField: `ml.inference.${mockModelConfiguration.configuration.destinationField}`,
|
||||
},
|
||||
],
|
||||
modelId: nerModel.model_id,
|
||||
pipelineDefinition: expect.any(Object), // Generation logic is tested elsewhere
|
||||
pipelineName: mockModelConfiguration.configuration.pipelineName,
|
||||
});
|
||||
|
|
|
@ -93,11 +93,23 @@ export const EMPTY_PIPELINE_CONFIGURATION: InferencePipelineConfiguration = {
|
|||
modelID: '',
|
||||
pipelineName: '',
|
||||
sourceField: '',
|
||||
targetField: '',
|
||||
};
|
||||
|
||||
const API_REQUEST_COMPLETE_STATUSES = [Status.SUCCESS, Status.ERROR];
|
||||
const DEFAULT_CONNECTOR_FIELDS = ['body', 'title', 'id', 'type', 'url'];
|
||||
|
||||
const getFullTargetFieldName = (
|
||||
sourceField: string,
|
||||
targetField: string | undefined,
|
||||
isTextExpansionModelSelected: boolean
|
||||
) => {
|
||||
const suffixedTargetField = `${targetField || sourceField}${
|
||||
isTextExpansionModelSelected ? '_expanded' : ''
|
||||
}`;
|
||||
return getMlInferencePrefixedFieldName(suffixedTargetField);
|
||||
};
|
||||
|
||||
export interface MLInferencePipelineOption {
|
||||
disabled: boolean;
|
||||
disabledReason?: string;
|
||||
|
@ -109,7 +121,9 @@ export interface MLInferencePipelineOption {
|
|||
}
|
||||
|
||||
interface MLInferenceProcessorsActions {
|
||||
addSelectedFieldsToMapping: () => void;
|
||||
addSelectedFieldsToMapping: (isTextExpansionModelSelected: boolean) => {
|
||||
isTextExpansionModelSelected: boolean;
|
||||
};
|
||||
attachApiError: Actions<
|
||||
AttachMlInferencePipelineApiLogicArgs,
|
||||
AttachMlInferencePipelineResponse
|
||||
|
@ -166,6 +180,7 @@ interface MLInferenceProcessorsActions {
|
|||
setInferencePipelineConfiguration: (configuration: InferencePipelineConfiguration) => {
|
||||
configuration: InferencePipelineConfiguration;
|
||||
};
|
||||
setTargetField: (targetFieldName: string) => { targetFieldName: string };
|
||||
startTextExpansionModelSuccess: StartTextExpansionModelApiLogicActions['apiSuccess'];
|
||||
}
|
||||
|
||||
|
@ -203,7 +218,9 @@ export const MLInferenceLogic = kea<
|
|||
MakeLogicType<MLInferenceProcessorsValues, MLInferenceProcessorsActions>
|
||||
>({
|
||||
actions: {
|
||||
addSelectedFieldsToMapping: true,
|
||||
addSelectedFieldsToMapping: (isTextExpansionModelSelected: string) => ({
|
||||
isTextExpansionModelSelected,
|
||||
}),
|
||||
attachPipeline: true,
|
||||
clearFormErrors: true,
|
||||
createPipeline: true,
|
||||
|
@ -217,6 +234,7 @@ export const MLInferenceLogic = kea<
|
|||
setInferencePipelineConfiguration: (configuration: InferencePipelineConfiguration) => ({
|
||||
configuration,
|
||||
}),
|
||||
setTargetField: (targetFieldName: string) => ({ targetFieldName }),
|
||||
},
|
||||
connect: {
|
||||
actions: [
|
||||
|
@ -298,6 +316,7 @@ export const MLInferenceLogic = kea<
|
|||
targetField: getMlInferencePrefixedFieldName(configuration.destinationField),
|
||||
},
|
||||
],
|
||||
modelId: configuration.modelID,
|
||||
pipelineDefinition: mlInferencePipeline!,
|
||||
pipelineName: configuration.pipelineName,
|
||||
});
|
||||
|
@ -314,6 +333,7 @@ export const MLInferenceLogic = kea<
|
|||
pipelineName,
|
||||
sourceField: params.source_field,
|
||||
fieldMappings: params.field_mappings,
|
||||
targetField: params.destination_field ?? '',
|
||||
});
|
||||
},
|
||||
setIndexName: ({ indexName }) => {
|
||||
|
@ -370,17 +390,21 @@ export const MLInferenceLogic = kea<
|
|||
step: AddInferencePipelineSteps.Configuration,
|
||||
},
|
||||
{
|
||||
addSelectedFieldsToMapping: (modal) => {
|
||||
addSelectedFieldsToMapping: (modal, { isTextExpansionModelSelected }) => {
|
||||
const {
|
||||
configuration: { fieldMappings },
|
||||
configuration: { fieldMappings, targetField },
|
||||
selectedSourceFields,
|
||||
} = modal;
|
||||
|
||||
const mergedFieldMappings: FieldMapping[] = [
|
||||
...(fieldMappings || []),
|
||||
...(selectedSourceFields || []).map((fieldName) => ({
|
||||
...(selectedSourceFields || []).map((fieldName: string) => ({
|
||||
sourceField: fieldName,
|
||||
targetField: getMlInferencePrefixedFieldName(`${fieldName}_expanded`),
|
||||
targetField: getFullTargetFieldName(
|
||||
fieldName,
|
||||
targetField,
|
||||
isTextExpansionModelSelected
|
||||
),
|
||||
})),
|
||||
];
|
||||
|
||||
|
@ -389,6 +413,7 @@ export const MLInferenceLogic = kea<
|
|||
configuration: {
|
||||
...modal.configuration,
|
||||
fieldMappings: mergedFieldMappings,
|
||||
targetField: '',
|
||||
},
|
||||
selectedSourceFields: [],
|
||||
};
|
||||
|
@ -437,6 +462,13 @@ export const MLInferenceLogic = kea<
|
|||
...modal,
|
||||
configuration,
|
||||
}),
|
||||
setTargetField: (modal, { targetFieldName }) => ({
|
||||
...modal,
|
||||
configuration: {
|
||||
...modal.configuration,
|
||||
targetField: targetFieldName,
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
createErrors: [
|
||||
|
|
|
@ -11,7 +11,7 @@ import React from 'react';
|
|||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiBasicTable, EuiButton, EuiComboBox } from '@elastic/eui';
|
||||
import { EuiBasicTable, EuiButton, EuiComboBox, EuiFieldText } from '@elastic/eui';
|
||||
|
||||
import { MultiFieldMapping, SelectedFieldMappings } from './multi_field_selector';
|
||||
|
||||
|
@ -92,6 +92,53 @@ describe('MultiFieldMapping', () => {
|
|||
const button = wrapper.find(EuiButton);
|
||||
expect(button.prop('disabled')).toBe(false);
|
||||
});
|
||||
it('disables target field text field if no source fields are selected', () => {
|
||||
setMockValues(DEFAULT_VALUES);
|
||||
const wrapper = shallow(<MultiFieldMapping />);
|
||||
|
||||
expect(wrapper.find(EuiFieldText)).toHaveLength(1);
|
||||
const textField = wrapper.find(EuiFieldText);
|
||||
expect(textField.prop('disabled')).toBe(true);
|
||||
});
|
||||
it('disables target field text field if multiple source fields are selected', () => {
|
||||
setMockValues({
|
||||
...DEFAULT_VALUES,
|
||||
addInferencePipelineModal: {
|
||||
...DEFAULT_VALUES.addInferencePipelineModal,
|
||||
selectedSourceFields: ['my-source-field1', 'my-source-field2'],
|
||||
},
|
||||
});
|
||||
const wrapper = shallow(<MultiFieldMapping />);
|
||||
|
||||
expect(wrapper.find(EuiFieldText)).toHaveLength(1);
|
||||
const textField = wrapper.find(EuiFieldText);
|
||||
expect(textField.prop('disabled')).toBe(true);
|
||||
});
|
||||
it('disables target field text field if text expansion model is selected', () => {
|
||||
setMockValues({
|
||||
...DEFAULT_VALUES,
|
||||
isTextExpansionModelSelected: true,
|
||||
});
|
||||
const wrapper = shallow(<MultiFieldMapping />);
|
||||
|
||||
expect(wrapper.find(EuiFieldText)).toHaveLength(1);
|
||||
const textField = wrapper.find(EuiFieldText);
|
||||
expect(textField.prop('disabled')).toBe(true);
|
||||
});
|
||||
it('enables target field text field if a single source field is selected', () => {
|
||||
setMockValues({
|
||||
...DEFAULT_VALUES,
|
||||
addInferencePipelineModal: {
|
||||
...DEFAULT_VALUES.addInferencePipelineModal,
|
||||
selectedSourceFields: ['my-source-field1'],
|
||||
},
|
||||
});
|
||||
const wrapper = shallow(<MultiFieldMapping />);
|
||||
|
||||
expect(wrapper.find(EuiFieldText)).toHaveLength(1);
|
||||
const textField = wrapper.find(EuiFieldText);
|
||||
expect(textField.prop('disabled')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('SelectedFieldMappings', () => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { useValues, useActions } from 'kea';
|
||||
|
||||
|
@ -31,13 +31,62 @@ import { MLInferenceLogic } from './ml_inference_logic';
|
|||
|
||||
type FieldNames = Array<{ label: string }>;
|
||||
|
||||
const TARGET_FIELD_PLACEHOLDER_TEXT_NO_FIELDS = i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.placeholder.noFields',
|
||||
{
|
||||
defaultMessage: 'Select a source field',
|
||||
}
|
||||
);
|
||||
|
||||
const TARGET_FIELD_PLACEHOLDER_TEXT_MULTIPLE_FIELDS = i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.placeholder.multipleFields',
|
||||
{
|
||||
defaultMessage: 'Automatically created for multi-select',
|
||||
}
|
||||
);
|
||||
|
||||
const TARGET_FIELD_PLACEHOLDER_TEXT_TEXT_EXPANSION_MODEL = i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.placeholder.textExpansionModel',
|
||||
{
|
||||
defaultMessage: 'Automatically created',
|
||||
}
|
||||
);
|
||||
|
||||
const TARGET_FIELD_HELP_TEXT_DEFAULT = i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.helpText',
|
||||
{
|
||||
defaultMessage: 'Optional. Field name where inference results should be saved.',
|
||||
}
|
||||
);
|
||||
|
||||
const TARGET_FIELD_HELP_TEXT_TEXT_EXPANSION_MODEL = i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.helpTextTextExpansionModel',
|
||||
{
|
||||
defaultMessage: 'ELSER target fields are created automatically.',
|
||||
}
|
||||
);
|
||||
|
||||
const getInitialTargetFieldPlaceholderText = (isTextExpansionModelSelected: boolean) =>
|
||||
isTextExpansionModelSelected
|
||||
? TARGET_FIELD_PLACEHOLDER_TEXT_TEXT_EXPANSION_MODEL
|
||||
: TARGET_FIELD_PLACEHOLDER_TEXT_NO_FIELDS;
|
||||
|
||||
const getTargetFieldHelpText = (isTextExpansionModelSelected: boolean) =>
|
||||
isTextExpansionModelSelected
|
||||
? TARGET_FIELD_HELP_TEXT_TEXT_EXPANSION_MODEL
|
||||
: TARGET_FIELD_HELP_TEXT_DEFAULT;
|
||||
|
||||
export const MultiFieldMapping: React.FC = () => {
|
||||
const {
|
||||
addInferencePipelineModal: { configuration, selectedSourceFields = [] },
|
||||
isTextExpansionModelSelected,
|
||||
sourceFields,
|
||||
} = useValues(MLInferenceLogic);
|
||||
const { ingestionMethod } = useValues(IndexViewLogic);
|
||||
const { addSelectedFieldsToMapping, selectFields } = useActions(MLInferenceLogic);
|
||||
const { addSelectedFieldsToMapping, selectFields, setTargetField } = useActions(MLInferenceLogic);
|
||||
const [placeholderText, setPlaceholderText] = useState<string>(
|
||||
getInitialTargetFieldPlaceholderText(isTextExpansionModelSelected)
|
||||
);
|
||||
|
||||
const mappedSourceFields =
|
||||
configuration.fieldMappings?.map(({ sourceField }) => sourceField) ?? [];
|
||||
|
@ -50,9 +99,25 @@ export const MultiFieldMapping: React.FC = () => {
|
|||
const selectedFields = selectedSourceFields.map((fieldName) => ({
|
||||
label: fieldName,
|
||||
}));
|
||||
const targetField = configuration.targetField;
|
||||
const isExactlyOneSourceFieldSelected = selectedSourceFields.length === 1;
|
||||
|
||||
const onChangeSelectedFields = (selectedFieldNames: FieldNames) => {
|
||||
selectFields(selectedFieldNames.map(({ label }) => label));
|
||||
setTargetField(
|
||||
!isTextExpansionModelSelected && selectedFieldNames.length === 1
|
||||
? selectedFieldNames[0].label
|
||||
: ''
|
||||
);
|
||||
setPlaceholderText(
|
||||
isTextExpansionModelSelected
|
||||
? TARGET_FIELD_PLACEHOLDER_TEXT_TEXT_EXPANSION_MODEL
|
||||
: selectedFieldNames.length === 0
|
||||
? TARGET_FIELD_PLACEHOLDER_TEXT_NO_FIELDS
|
||||
: selectedFieldNames.length === 1
|
||||
? selectedFieldNames[0].label
|
||||
: TARGET_FIELD_PLACEHOLDER_TEXT_MULTIPLE_FIELDS
|
||||
);
|
||||
};
|
||||
|
||||
const onCreateField = (fieldName: string) => {
|
||||
|
@ -63,6 +128,12 @@ export const MultiFieldMapping: React.FC = () => {
|
|||
selectFields([...selectedSourceFields, fieldName]);
|
||||
};
|
||||
|
||||
const onAddSelectedFields = () => {
|
||||
addSelectedFieldsToMapping(isTextExpansionModelSelected);
|
||||
setTargetField('');
|
||||
setPlaceholderText(getInitialTargetFieldPlaceholderText(isTextExpansionModelSelected));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
|
@ -109,23 +180,16 @@ export const MultiFieldMapping: React.FC = () => {
|
|||
defaultMessage: 'Target field',
|
||||
}
|
||||
)}
|
||||
helpText={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.helpText',
|
||||
{
|
||||
defaultMessage: 'This name is automatically created based on your source field.',
|
||||
}
|
||||
)}
|
||||
helpText={getTargetFieldHelpText(isTextExpansionModelSelected)}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
prepend="ml.inference."
|
||||
onChange={(e) => setTargetField(e.target.value)}
|
||||
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-configureFields-targetField`}
|
||||
disabled
|
||||
value={i18n.translate(
|
||||
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.defaultValue',
|
||||
{
|
||||
defaultMessage: 'This is automatically created',
|
||||
}
|
||||
)}
|
||||
disabled={isTextExpansionModelSelected || !isExactlyOneSourceFieldSelected}
|
||||
value={targetField}
|
||||
placeholder={placeholderText}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -136,7 +200,7 @@ export const MultiFieldMapping: React.FC = () => {
|
|||
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-configureFields-addSelectedFieldsToMapping`}
|
||||
disabled={selectedFields.length === 0}
|
||||
iconType="plusInCircle"
|
||||
onClick={addSelectedFieldsToMapping}
|
||||
onClick={onAddSelectedFields}
|
||||
style={{ width: '60px' }}
|
||||
>
|
||||
{i18n.translate(
|
||||
|
|
|
@ -25,6 +25,7 @@ const DEFAULT_VALUES: TestPipelineValues = {
|
|||
modelID: '',
|
||||
pipelineName: '',
|
||||
sourceField: '',
|
||||
targetField: '',
|
||||
},
|
||||
indexName: '',
|
||||
step: AddInferencePipelineSteps.Configuration,
|
||||
|
@ -70,6 +71,7 @@ describe('TestPipelineLogic', () => {
|
|||
modelID: '',
|
||||
pipelineName: '',
|
||||
sourceField: '',
|
||||
targetField: '',
|
||||
},
|
||||
indexName: '',
|
||||
step: AddInferencePipelineSteps.Configuration,
|
||||
|
|
|
@ -17,6 +17,7 @@ export interface InferencePipelineConfiguration {
|
|||
pipelineName: string;
|
||||
sourceField: string;
|
||||
fieldMappings?: FieldMapping[];
|
||||
targetField: string;
|
||||
}
|
||||
|
||||
export interface AddInferencePipelineFormErrors {
|
||||
|
|
|
@ -38,7 +38,7 @@ export const preparePipelineAndIndexForMlInference = async (
|
|||
indexName: string,
|
||||
pipelineName: string,
|
||||
pipelineDefinition: IngestPipeline | undefined,
|
||||
modelId: string | undefined,
|
||||
modelId: string,
|
||||
sourceField: string | undefined,
|
||||
destinationField: string | null | undefined,
|
||||
fieldMappings: FieldMapping[] | undefined,
|
||||
|
@ -62,7 +62,7 @@ export const preparePipelineAndIndexForMlInference = async (
|
|||
);
|
||||
|
||||
const mappingResponse = fieldMappings
|
||||
? (await updateMlInferenceMappings(indexName, fieldMappings, esClient)).acknowledged
|
||||
? (await updateMlInferenceMappings(indexName, modelId, fieldMappings, esClient)).acknowledged
|
||||
: false;
|
||||
|
||||
return {
|
||||
|
|
|
@ -13,11 +13,27 @@ import { updateMlInferenceMappings } from './update_ml_inference_mappings';
|
|||
|
||||
describe('updateMlInferenceMappings', () => {
|
||||
const indexName = 'my-index';
|
||||
|
||||
const modelId = 'my-model-id';
|
||||
const mockClient = elasticsearchServiceMock.createScopedClusterClient();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockClient.asCurrentUser.ml.getTrainedModels.mockResolvedValue({
|
||||
count: 1,
|
||||
trained_model_configs: [
|
||||
{
|
||||
inference_config: {
|
||||
text_expansion: {},
|
||||
},
|
||||
input: {
|
||||
field_names: [],
|
||||
},
|
||||
model_id: modelId,
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
const expectedMapping = {
|
||||
|
@ -62,14 +78,42 @@ describe('updateMlInferenceMappings', () => {
|
|||
},
|
||||
];
|
||||
|
||||
it('should update mappings for default output', async () => {
|
||||
await updateMlInferenceMappings(indexName, fieldMappings, mockClient.asCurrentUser);
|
||||
it('should update mappings for text expansion pipelines', async () => {
|
||||
await updateMlInferenceMappings(indexName, modelId, fieldMappings, mockClient.asCurrentUser);
|
||||
expect(mockClient.asCurrentUser.indices.putMapping).toHaveBeenLastCalledWith({
|
||||
index: indexName,
|
||||
properties: expectedMapping,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not update mappings for pipelines other than text expansion', async () => {
|
||||
const nonTextExpansionModelId = 'some-other-model-id';
|
||||
|
||||
mockClient.asCurrentUser.ml.getTrainedModels.mockResolvedValue({
|
||||
count: 1,
|
||||
trained_model_configs: [
|
||||
{
|
||||
inference_config: {
|
||||
ner: {},
|
||||
},
|
||||
input: {
|
||||
field_names: [],
|
||||
},
|
||||
model_id: nonTextExpansionModelId,
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await updateMlInferenceMappings(
|
||||
indexName,
|
||||
nonTextExpansionModelId,
|
||||
fieldMappings,
|
||||
mockClient.asCurrentUser
|
||||
);
|
||||
expect(mockClient.asCurrentUser.indices.putMapping).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should raise an error if the update fails', async () => {
|
||||
mockClient.asCurrentUser.indices.putMapping.mockImplementation(() =>
|
||||
Promise.reject({
|
||||
|
@ -84,7 +128,7 @@ describe('updateMlInferenceMappings', () => {
|
|||
})
|
||||
);
|
||||
await expect(
|
||||
updateMlInferenceMappings(indexName, fieldMappings, mockClient.asCurrentUser)
|
||||
updateMlInferenceMappings(indexName, modelId, fieldMappings, mockClient.asCurrentUser)
|
||||
).rejects.toThrowError(ErrorCode.MAPPING_UPDATE_FAILED);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,9 +21,17 @@ import { isIllegalArgumentException } from '../../../../utils/identify_exception
|
|||
*/
|
||||
export const updateMlInferenceMappings = async (
|
||||
indexName: string,
|
||||
modelId: string,
|
||||
fieldMappings: FieldMapping[],
|
||||
esClient: ElasticsearchClient
|
||||
) => {
|
||||
// Check if the model is of text_expansion type, if not, skip the mapping update
|
||||
if (!(await isTextExpansionModel(modelId, esClient))) {
|
||||
return {
|
||||
acknowledged: false,
|
||||
};
|
||||
}
|
||||
|
||||
const sourceFields = fieldMappings.map(({ sourceField }) => sourceField);
|
||||
|
||||
const nonDefaultTargetFields = fieldMappings
|
||||
|
@ -97,3 +105,9 @@ const formDefaultElserMappingProps = (sourceFields: string[]) => {
|
|||
{}
|
||||
);
|
||||
};
|
||||
|
||||
const isTextExpansionModel = async (modelId: string, esClient: ElasticsearchClient) => {
|
||||
const models = await esClient.ml.getTrainedModels({ model_id: modelId });
|
||||
|
||||
return models.trained_model_configs[0]?.inference_config?.text_expansion !== undefined;
|
||||
};
|
||||
|
|
|
@ -409,7 +409,7 @@ export function registerIndexRoutes({
|
|||
),
|
||||
})
|
||||
),
|
||||
model_id: schema.maybe(schema.string()),
|
||||
model_id: schema.string(),
|
||||
pipeline_definition: schema.maybe(
|
||||
schema.object({
|
||||
description: schema.maybe(schema.string()),
|
||||
|
@ -437,14 +437,14 @@ export function registerIndexRoutes({
|
|||
} = request.body;
|
||||
|
||||
// additional validations
|
||||
if ((pipelineDefinition || fieldMappings) && (sourceField || destinationField || modelId)) {
|
||||
if ((pipelineDefinition || fieldMappings) && (sourceField || destinationField)) {
|
||||
return createError({
|
||||
errorCode: ErrorCode.PARAMETER_CONFLICT,
|
||||
message: i18n.translate(
|
||||
'xpack.enterpriseSearch.server.routes.createMlInferencePipeline.ParameterConflictError',
|
||||
{
|
||||
defaultMessage:
|
||||
'pipeline_definition and field_mappings should only be provided if source_field and destination_field and model_id are not provided',
|
||||
'pipeline_definition and field_mappings should only be provided if source_field and destination_field are not provided',
|
||||
}
|
||||
),
|
||||
response,
|
||||
|
|
|
@ -13596,7 +13596,6 @@
|
|||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.sourceField.helpText": "Sélectionnez un champ existant ou tapez un nom de champ.",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.sourceField.placeholder": "Sélectionner un champ de schéma",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.sourceFieldLabel": "Champ source",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.defaultValue": "Ceci est créé automatiquement",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.helpText": "Ce nom est créé automatiquement en fonction de votre champ source.",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.label": "Champ cible (facultatif)",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetFieldLabel": "Champ cible",
|
||||
|
|
|
@ -13610,7 +13610,6 @@
|
|||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.sourceField.helpText": "既存のフィールドを選択するか、フィールド名を入力してください。",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.sourceField.placeholder": "スキーマフィールドを選択",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.sourceFieldLabel": "ソースフィールド",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.defaultValue": "これは自動的に作成されます",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.helpText": "この名前は、ソースフィールドに基づいて自動的に作成されます。",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.label": "ターゲットフィールド(任意)",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetFieldLabel": "ターゲットフィールド",
|
||||
|
|
|
@ -13609,7 +13609,6 @@
|
|||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.sourceField.helpText": "选择现有字段或键入字段名称。",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.sourceField.placeholder": "选择架构字段",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.sourceFieldLabel": "源字段",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.defaultValue": "这是自动创建的内容",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.helpText": "此名称基于您的源字段自动创建。",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetField.label": "目标字段(可选)",
|
||||
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.fields.targetFieldLabel": "目标字段",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue