diff --git a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/is_json.ts b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/is_json.ts index 5626fc80bb749..dc8321aa07004 100644 --- a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/is_json.ts +++ b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/is_json.ts @@ -17,25 +17,6 @@ * under the License. */ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - import { ValidationFunc } from '../../hook_form_lib'; import { isJSON } from '../../../validators/string'; import { ERROR_CODE } from './types'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts index 663017e2e47af..cc4c17c5c63a3 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/index.ts @@ -63,6 +63,10 @@ export * from './max_shingle_size_parameter'; export * from './relations_parameter'; +export * from './other_type_name_parameter'; + +export * from './other_type_json_parameter'; + export const PARAMETER_SERIALIZERS = [relationsSerializer, dynamicSerializer]; export const PARAMETER_DESERIALIZERS = [relationsDeserializer, dynamicDeserializer]; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_json_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_json_parameter.tsx new file mode 100644 index 0000000000000..64e50f711a249 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_json_parameter.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; + +import { + UseField, + JsonEditorField, + ValidationFuncArg, + fieldValidators, + FieldConfig, +} from '../../../shared_imports'; + +const { isJsonField } = fieldValidators; + +/** + * This is a special component that does not have an explicit entry in {@link PARAMETERS_DEFINITION}. + * + * We use it to store custom defined parameters in a field called "otherTypeJson". + */ + +const fieldConfig: FieldConfig = { + label: i18n.translate('xpack.idxMgmt.mappingsEditor.otherTypeJsonFieldLabel', { + defaultMessage: 'Type Parameters JSON', + }), + defaultValue: {}, + validations: [ + { + validator: isJsonField( + i18n.translate( + 'xpack.idxMgmt.mappingsEditor.parameters.validations.otherTypeJsonInvalidJSONErrorMessage', + { + defaultMessage: 'Invalid JSON.', + } + ) + ), + }, + { + validator: ({ value }: ValidationFuncArg) => { + const json = JSON.parse(value); + if (Array.isArray(json)) { + return { + message: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.parameters.validations.otherTypeJsonArrayNotAllowedErrorMessage', + { + defaultMessage: 'Arrays are not allowed.', + } + ), + }; + } + }, + }, + { + validator: ({ value }: ValidationFuncArg) => { + const json = JSON.parse(value); + if (json.type) { + return { + code: 'ERR_CUSTOM_TYPE_OVERRIDDEN', + message: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.parameters.validations.otherTypeJsonTypeFieldErrorMessage', + { + defaultMessage: 'Cannot override the "type" field.', + } + ), + }; + } + }, + }, + ], + deserializer: (value: any) => { + if (value === '') { + return value; + } + return JSON.stringify(value, null, 2); + }, + serializer: (value: string) => { + try { + return JSON.parse(value); + } catch (error) { + // swallow error and return non-parsed value; + return value; + } + }, +}; + +export const OtherTypeJsonParameter = () => ( + +); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_name_parameter.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_name_parameter.tsx new file mode 100644 index 0000000000000..6004e484323a1 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/other_type_name_parameter.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { UseField, TextField, FieldConfig } from '../../../shared_imports'; +import { fieldValidators } from '../../../shared_imports'; + +const { emptyField } = fieldValidators; + +/** + * This is a special component that does not have an explicit entry in {@link PARAMETERS_DEFINITION}. + * + * We use it to store the name of types unknown to the mappings editor in the "subType" path. + */ + +const fieldConfig: FieldConfig = { + label: i18n.translate('xpack.idxMgmt.mappingsEditor.otherTypeNameFieldLabel', { + defaultMessage: 'Type Name', + }), + defaultValue: '', + validations: [ + { + validator: emptyField( + i18n.translate( + 'xpack.idxMgmt.mappingsEditor.parameters.validations.otherTypeNameIsRequiredErrorMessage', + { + defaultMessage: 'The type name is required.', + } + ) + ), + }, + ], +}; + +export const OtherTypeNameParameter = () => ( + +); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx index 60b025ce644ef..b41f35b983885 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx @@ -5,6 +5,7 @@ */ import React, { useEffect, useCallback } from 'react'; import classNames from 'classnames'; +import * as _ from 'lodash'; import { i18n } from '@kbn/i18n'; @@ -31,7 +32,7 @@ import { filterTypesForNonRootFields, } from '../../../../lib'; import { Field, MainType, SubType, NormalizedFields, ComboBoxOption } from '../../../../types'; -import { NameParameter, TypeParameter } from '../../field_parameters'; +import { NameParameter, TypeParameter, OtherTypeNameParameter } from '../../field_parameters'; import { getParametersFormForType } from './required_parameters_forms'; const formWrapper = (props: any) =>
; @@ -155,9 +156,9 @@ export const CreateField = React.memo(function CreateFieldComponent({ }, [form, getSubTypeMeta] ); - const renderFormFields = useCallback( ({ type }) => { + const isOtherType = type === 'other'; const { subTypeOptions, subTypeLabel } = getSubTypeMeta(type); const docLink = documentationService.getTypeDocLink(type) as string; @@ -178,7 +179,13 @@ export const CreateField = React.memo(function CreateFieldComponent({ docLink={docLink} /> - {/* Field sub type (if any) */} + {/* Other type */} + {isOtherType && ( + + + + )} + {/* Field sub type (if any) - will never be the case if we have an "other" type */} {subTypeOptions && ( {/* Documentation link */} - - - {i18n.translate( - 'xpack.idxMgmt.mappingsEditor.editField.typeDocumentation', - { - defaultMessage: '{type} documentation', - values: { - type: subTypeDefinition - ? subTypeDefinition.label - : typeDefinition.label, - }, - } - )} - - + {linkDocumentation && ( + + + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.editField.typeDocumentation', + { + defaultMessage: '{type} documentation', + values: { + type: subTypeDefinition + ? subTypeDefinition.label + : typeDefinition.label, + }, + } + )} + + + )} {/* Field path */} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx index ddb808094428d..75a083d64b6db 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx @@ -17,7 +17,7 @@ import { } from '../../../../lib'; import { TYPE_DEFINITION } from '../../../../constants'; -import { NameParameter, TypeParameter } from '../../field_parameters'; +import { NameParameter, TypeParameter, OtherTypeNameParameter } from '../../field_parameters'; import { FieldDescriptionSection } from './field_description_section'; interface Props { @@ -80,9 +80,17 @@ export const EditFieldHeaderForm = React.memo( /> - {/* Field sub type (if any) */} + {/* Other type */} + {type === 'other' && ( + + + + )} + + {/* Field sub type (if any) - will never be the case if we have an "other" type */} {hasSubType && ( + {' '} } = { shape: ShapeType, dense_vector: DenseVectorType, object: ObjectType, + other: OtherType, nested: NestedType, join: JoinType, }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/other_type.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/other_type.tsx new file mode 100644 index 0000000000000..c403bbfb79056 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/field_types/other_type.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; + +import { OtherTypeJsonParameter } from '../../field_parameters'; +import { BasicParametersSection } from '../edit_field'; + +export const OtherType = () => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx index fca40513b4e2b..ad874a672bdbe 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/fields_list_item.tsx @@ -16,12 +16,15 @@ import { import { i18n } from '@kbn/i18n'; import { NormalizedField, NormalizedFields } from '../../../types'; +import { getTypeLabelFromType } from '../../../lib'; import { TYPE_DEFINITION, CHILD_FIELD_INDENT_SIZE, LEFT_PADDING_SIZE_FIELD_ITEM_WRAPPER, } from '../../../constants'; + import { ChainedMultifieldsWarning } from '../../chained_multifields_warning'; + import { FieldsList } from './fields_list'; import { CreateField } from './create_field'; import { DeleteFieldProvider } from './delete_field_provider'; @@ -268,7 +271,7 @@ function FieldListItemComponent( dataType: TYPE_DEFINITION[source.type].label, }, }) - : TYPE_DEFINITION[source.type].label} + : getTypeLabelFromType(source.type)} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx index dbb8a788514bc..614b7cb56bef6 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/search_fields/search_result_item.tsx @@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n'; import { SearchResult } from '../../../types'; import { TYPE_DEFINITION } from '../../../constants'; import { useDispatch } from '../../../mappings_state'; +import { getTypeLabelFromType } from '../../../lib'; import { DeleteFieldProvider } from '../fields/delete_field_provider'; interface Props { @@ -115,7 +116,7 @@ export const SearchResultItem = React.memo(function FieldListItemFlatComponent({ dataType: TYPE_DEFINITION[source.type].label, }, }) - : TYPE_DEFINITION[source.type].label} + : getTypeLabelFromType(source.type)} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx index f904281181c48..4206fe8b696da 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/constants/data_types_definition.tsx @@ -784,6 +784,20 @@ export const TYPE_DEFINITION: { [key in DataType]: DataTypeDefinition } = {

), }, + other: { + label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.otherDescription', { + defaultMessage: 'Other', + }), + value: 'other', + description: () => ( +

+ +

+ ), + }, }; export const MAIN_TYPES: MainType[] = [ @@ -811,6 +825,7 @@ export const MAIN_TYPES: MainType[] = [ 'shape', 'text', 'token_count', + 'other', ]; export const MAIN_DATA_TYPE_DEFINITION: { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/search_fields.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/search_fields.tsx index 5a277073c5f1a..618d106b0e7a1 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/search_fields.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/search_fields.tsx @@ -185,8 +185,6 @@ const getSearchMetadata = (searchData: SearchData, fieldData: FieldData): Search const score = calculateScore(metadata); const display = getJSXdisplayFromMeta(searchData, fieldData, metadata); - // console.log(fieldData.path, score, metadata); - return { ...metadata, display, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/serializers.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/serializers.ts index 131d886ff05d9..6b817c829251f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/serializers.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/serializers.ts @@ -45,16 +45,19 @@ const runParametersDeserializers = (field: Field): Field => ); export const fieldSerializer: SerializerFunc = (field: Field) => { + const { otherTypeJson, ...rest } = field; + const updatedField: Field = Boolean(otherTypeJson) ? { ...otherTypeJson, ...rest } : { ...rest }; + // If a subType is present, use it as type for ES - if ({}.hasOwnProperty.call(field, 'subType')) { - field.type = field.subType as DataType; - delete field.subType; + if ({}.hasOwnProperty.call(updatedField, 'subType')) { + updatedField.type = updatedField.subType as DataType; + delete updatedField.subType; } // Delete temp fields - delete (field as any).useSameAnalyzerForSearch; + delete (updatedField as any).useSameAnalyzerForSearch; - return sanitizeField(runParametersSerializers(field)); + return sanitizeField(runParametersSerializers(updatedField)); }; export const fieldDeserializer: SerializerFunc = (field: Field): Field => { @@ -70,8 +73,18 @@ export const fieldDeserializer: SerializerFunc = (field: Field): Field => field.type = type; } - (field as any).useSameAnalyzerForSearch = - {}.hasOwnProperty.call(field, 'search_analyzer') === false; + if (field.type === 'other') { + const { type, subType, name, ...otherTypeJson } = field; + /** + * For "other" type (type we don't support through a form) + * we grab all the parameters and put them in the "otherTypeJson" object + * that we will render in a JSON editor. + */ + field.otherTypeJson = otherTypeJson; + } else { + (field as any).useSameAnalyzerForSearch = + {}.hasOwnProperty.call(field, 'search_analyzer') === false; + } return runParametersDeserializers(field); }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts index e8f63265d378f..6bb3c4bc23807 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.ts @@ -25,6 +25,7 @@ import { PARAMETERS_DEFINITION, TYPE_NOT_ALLOWED_MULTIFIELD, TYPE_ONLY_ALLOWED_AT_ROOT_LEVEL, + TYPE_DEFINITION, } from '../constants'; import { State } from '../reducer'; @@ -70,6 +71,9 @@ export const getFieldMeta = (field: Field, isMultiField?: boolean): FieldMeta => }; }; +export const getTypeLabelFromType = (type: DataType) => + TYPE_DEFINITION[type] ? TYPE_DEFINITION[type].label : `${TYPE_DEFINITION.other.label}: ${type}`; + export const getFieldConfig = (param: ParameterName, prop?: string): FieldConfig => { if (prop !== undefined) { if ( @@ -121,7 +125,7 @@ const replaceAliasPathByAliasId = ( }; export const getMainTypeFromSubType = (subType: SubType): MainType => - SUB_TYPE_MAP_TO_MAIN[subType] as MainType; + (SUB_TYPE_MAP_TO_MAIN[subType] ?? 'other') as MainType; /** * In order to better work with the recursive pattern of the mappings `properties`, this method flatten the fields @@ -286,7 +290,9 @@ export const deNormalize = ({ rootLevelFields, byId, aliases }: NormalizedFields const { source, childFields, childFieldsName } = serializedFieldsById[id]; const { name, ...normalizedField } = source; const field: Omit = normalizedField; + to[name] = field; + if (childFields) { field[childFieldsName!] = {}; return deNormalizePaths(childFields, field[childFieldsName!]); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types.ts index dbbffe5a0bd31..5b18af68ed55b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types.ts @@ -56,7 +56,12 @@ export type MainType = | 'date_nanos' | 'geo_point' | 'geo_shape' - | 'token_count'; + | 'token_count' + /** + * 'other' is a special type that only exists inside of MappingsEditor as a placeholder + * for undocumented field types. + */ + | 'other'; export type SubType = NumericType | RangeType; @@ -156,6 +161,10 @@ interface FieldBasic { subType?: SubType; properties?: { [key: string]: Omit }; fields?: { [key: string]: Omit }; + + // other* exist together as a holder of types that the mappings editor does not yet know about but + // enables the user to create mappings with them. + otherTypeJson?: GenericObject; } type FieldParams = {