[Search] Make Reference Field in Semantic_text Optional (#215562)

## Summary

This PR focus on making the `reference field` optional when adding
`semantic_text` field from Index Mapping. Previously, `semantic_text`
field was dependent on a text field and `copy_to` functionality which is
not required anymore.


https://github.com/user-attachments/assets/f19d0c1b-ac34-4f8d-b75d-993dd8720739


Added a label `optional` in the field after the video recording.
<img width="1227" alt="Screenshot 2025-03-26 at 1 25 22 PM"
src="https://github.com/user-attachments/assets/a11ed104-df50-47f4-a13f-9cf7187b2ad1"
/>


### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.
- [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] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Samiul Monir 2025-03-27 17:10:41 -06:00 committed by GitHub
parent 40e95f00f1
commit ab07c23962
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 91 additions and 25 deletions

View file

@ -21834,7 +21834,6 @@
"xpack.idxMgmt.mappingsEditor.parameters.validations.pathIsRequiredErrorMessage": "Sélectionnez un champ vers lequel pointer l'alias.",
"xpack.idxMgmt.mappingsEditor.parameters.validations.positionIncrementGapIsRequiredErrorMessage": "Définir une valeur d'écart d'incrément de position",
"xpack.idxMgmt.mappingsEditor.parameters.validations.priorityIsRequiredErrorMessage": "Spécifiez une priorité.",
"xpack.idxMgmt.mappingsEditor.parameters.validations.referenceFieldIsRequiredErrorMessage": "Le champ de référence est obligatoire.",
"xpack.idxMgmt.mappingsEditor.parameters.validations.scalingFactorIsRequiredErrorMessage": "Facteur de montée en charge obligatoire.",
"xpack.idxMgmt.mappingsEditor.parameters.validations.smallerZeroErrorMessage": "La valeur doit être supérieure ou égale à 0.",
"xpack.idxMgmt.mappingsEditor.parameters.validations.spacesNotAllowedErrorMessage": "Les espaces ne sont pas autorisés.",

View file

@ -21813,7 +21813,6 @@
"xpack.idxMgmt.mappingsEditor.parameters.validations.pathIsRequiredErrorMessage": "エイリアスが指し示すフィールドを選択します。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.positionIncrementGapIsRequiredErrorMessage": "位置のインクリメントギャップ値の設定",
"xpack.idxMgmt.mappingsEditor.parameters.validations.priorityIsRequiredErrorMessage": "優先度を指定します。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.referenceFieldIsRequiredErrorMessage": "参照フィールドは必須です。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.scalingFactorIsRequiredErrorMessage": "スケーリングファクターが必要です。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.smallerZeroErrorMessage": "値は0と同じかそれ以上でなければなりません。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.spacesNotAllowedErrorMessage": "スペースは使用できません。",

View file

@ -21856,7 +21856,6 @@
"xpack.idxMgmt.mappingsEditor.parameters.validations.pathIsRequiredErrorMessage": "选择将别名指向的字段。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.positionIncrementGapIsRequiredErrorMessage": "设置位置递增间隔值",
"xpack.idxMgmt.mappingsEditor.parameters.validations.priorityIsRequiredErrorMessage": "指定优先级。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.referenceFieldIsRequiredErrorMessage": "“参考字段”必填。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.scalingFactorIsRequiredErrorMessage": "缩放因数必填。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.smallerZeroErrorMessage": "值必须大于或等于 0。",
"xpack.idxMgmt.mappingsEditor.parameters.validations.spacesNotAllowedErrorMessage": "不允许使用空格。",

View file

@ -128,7 +128,12 @@ const createActions = (testBed: TestBed<TestSubjects>) => {
return { field: find(testSubject as TestSubjects), testSubject };
};
const addField = async (name: string, type: string, subType?: string) => {
const addField = async (
name: string,
type: string,
subType?: string,
referenceFieldValue?: string
) => {
await act(async () => {
form.setInputValue('nameParameterInput', name);
jest.advanceTimersByTime(0); // advance timers to allow the form to validate
@ -151,6 +156,11 @@ const createActions = (testBed: TestBed<TestSubjects>) => {
});
}
if (referenceFieldValue !== undefined) {
form.setSelectValue('select', referenceFieldValue);
jest.advanceTimersByTime(0); // advance timers to allow the form to validate
}
await act(async () => {
find('createFieldForm.addButton').simulate('click');
jest.advanceTimersByTime(0); // advance timers to allow the form to validate

View file

@ -506,6 +506,44 @@ describe('Mappings editor: core', () => {
const newField = { name: 'someNewField', type: 'semantic_text' };
await addField(newField.name, newField.type);
updatedMappings = {
...updatedMappings,
properties: {
...updatedMappings.properties,
[newField.name]: { reference_field: '', type: 'semantic_text' },
},
};
({ data } = await getMappingsEditorData(component));
expect(data).toEqual(updatedMappings);
});
test('updates mapping with reference field value for semantic_text field', async () => {
let updatedMappings = { ...defaultMappings };
const {
find,
actions: { addField },
component,
} = testBed;
/**
* Mapped fields
*/
await act(async () => {
find('addFieldButton').simulate('click');
jest.advanceTimersByTime(0); // advance timers to allow the form to validate
});
component.update();
const newField = {
name: 'someNewField',
type: 'semantic_text',
referenceField: 'address.city',
};
await addField(newField.name, newField.type, undefined, newField.referenceField);
updatedMappings = {
...updatedMappings,
properties: {

View file

@ -7,6 +7,8 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiText } from '@elastic/eui';
import { getFieldConfig } from '../../../lib';
import { useMappingsState } from '../../../mappings_state_context';
import { SuperSelectField, UseField } from '../../../shared_imports';
@ -41,6 +43,16 @@ export const ReferenceFieldSelects = () => {
euiFieldProps={{
options: referenceFieldOptions,
}}
labelAppend={
<EuiText size="xs" color="subdued">
{i18n.translate(
'xpack.idxMgmt.mappingsEditor.createField.referenceField.optionalLabel',
{
defaultMessage: 'Optional',
}
)}
</EuiText>
}
data-test-subj="referenceFieldSelect"
/>
)}

View file

@ -127,15 +127,11 @@ export const CreateField = React.memo(function CreateFieldComponent({
const defaultName = getFieldByPathName(allSemanticFields, 'semantic_text')
? ''
: 'semantic_text';
const referenceField =
Object.values(allSemanticFields.byId)
.find((field) => field.source.type === 'text' && !field.isMultiField)
?.path.join('.') || '';
if (!form.getFormData().name) {
form.setFieldValue('name', defaultName);
}
if (!form.getFormData().reference_field) {
form.setFieldValue('reference_field', referenceField);
form.setFieldValue('reference_field', '');
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps

View file

@ -1109,18 +1109,6 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio
helpText: i18n.translate('xpack.idxMgmt.mappingsEditor.parameters.referenceFieldHelpText', {
defaultMessage: 'Reference field for model inference.',
}),
validations: [
{
validator: emptyField(
i18n.translate(
'xpack.idxMgmt.mappingsEditor.parameters.validations.referenceFieldIsRequiredErrorMessage',
{
defaultMessage: 'Reference field is required.',
}
)
),
},
],
},
schema: t.string,
},

View file

@ -462,8 +462,25 @@ describe('utils', () => {
} as any;
expect(getStateWithCopyToFields(state)).toEqual(state);
});
test('returns state if semantic text field has no reference field', () => {
test('returns state if reference field in semantic_text is empty', () => {
const state = {
fields: {
byId: {
'88ebcfdb-19b7-4458-9ea2-9488df54453d': {
id: '88ebcfdb-19b7-4458-9ea2-9488df54453d',
isMultiField: false,
source: {
name: 'title',
type: 'semantic_text',
inference_id: 'id',
reference_field: '',
},
},
},
},
} as any;
const expectedState = {
fields: {
byId: {
'88ebcfdb-19b7-4458-9ea2-9488df54453d': {
@ -478,7 +495,7 @@ describe('utils', () => {
},
},
} as any;
expect(getStateWithCopyToFields(state)).toEqual(state);
expect(getStateWithCopyToFields(state)).toEqual(expectedState);
});
test('adds text field with copy to to state if semantic text field has reference field', () => {
const state = {

View file

@ -7,7 +7,7 @@
import { v4 as uuidv4 } from 'uuid';
import { cloneDeep } from 'lodash';
import { cloneDeep, isEmpty } from 'lodash';
import { InferenceServiceSettings } from '@elastic/elasticsearch/lib/api/types';
import { LocalInferenceServiceSettings } from '@kbn/ml-trained-models-utils/src/constants/trained_models';
import {
@ -702,7 +702,7 @@ export function getStateWithCopyToFields(state: State): State {
// Make sure we don't accidentally modify existing state
let updatedState = cloneDeep(state);
for (const field of Object.values(updatedState.fields.byId)) {
if (field.source.type === 'semantic_text' && field.source.reference_field) {
if (field.source.type === 'semantic_text') {
// Check fields already added to the list of to-update fields first
// API will not accept reference_field so removing it now
const { reference_field: referenceField, ...source } = field.source;
@ -711,6 +711,14 @@ export function getStateWithCopyToFields(state: State): State {
throw new Error('Reference field is not a string');
}
field.source = source;
/*
If no reference field is associated,
no further processing is needed, so we can skip to the next one.
*/
if (isEmpty(referenceField)) {
continue;
}
const existingTextField =
getFieldByPathName(updatedState.fields, referenceField) ||
getFieldByPathName(updatedState.mappingViewFields || { byId: {} }, referenceField);