diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 4297d945f8804..b2323b07e38d1 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -430,6 +430,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D mappingSimilarity: `${ELASTICSEARCH_DOCS}similarity.html`, mappingSourceFields: `${ELASTICSEARCH_DOCS}mapping-source-field.html`, mappingSourceFieldsDisable: `${ELASTICSEARCH_DOCS}mapping-source-field.html#disable-source-field`, + mappingSyntheticSourceFields: `${ELASTICSEARCH_DOCS}mapping-source-field.html#synthetic-source`, mappingStore: `${ELASTICSEARCH_DOCS}mapping-store.html`, mappingSubobjects: `${ELASTICSEARCH_DOCS}subobjects.html`, mappingTermVector: `${ELASTICSEARCH_DOCS}term-vector.html`, diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 094fd40ab1e32..349a724043169 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -459,6 +459,7 @@ export type TestSubjects = | 'advancedConfiguration.dynamicMappingsToggle.input' | 'advancedConfiguration.metaField' | 'advancedConfiguration.routingRequiredToggle.input' + | 'sourceValueField' | 'sourceField.includesField' | 'sourceField.excludesField' | 'dynamicTemplatesEditor' diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 685aa4963edc4..ee3b3e72e7c19 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -28,7 +28,24 @@ describe('Mappings editor: core', () => { let onChangeHandler: jest.Mock = jest.fn(); let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler); let testBed: MappingsEditorTestBed; - const appDependencies = { plugins: { ml: { mlApi: {} } } }; + let hasEnterpriseLicense = true; + const mockLicenseCheck = jest.fn((type: any) => hasEnterpriseLicense); + const appDependencies = { + plugins: { + ml: { mlApi: {} }, + licensing: { + license$: { + subscribe: jest.fn((callback: any) => { + callback({ + isActive: true, + hasAtLeast: mockLicenseCheck, + }); + return { unsubscribe: jest.fn() }; + }), + }, + }, + }, + }; beforeAll(() => { jest.useFakeTimers({ legacyFakeTimers: true }); @@ -456,6 +473,11 @@ describe('Mappings editor: core', () => { updatedMappings = { ...updatedMappings, dynamic: false, + // The "enabled": true is removed as this is the default in Es + _source: { + includes: defaultMappings._source.includes, + excludes: defaultMappings._source.excludes, + }, }; delete updatedMappings.date_detection; delete updatedMappings.dynamic_date_formats; @@ -463,6 +485,84 @@ describe('Mappings editor: core', () => { expect(data).toEqual(updatedMappings); }); + + describe('props.indexMode sets the correct default value of _source field', () => { + it("defaults to 'stored' with 'standard' index mode prop", async () => { + await act(async () => { + testBed = setup( + { + value: { ...defaultMappings, _source: undefined }, + onChange: onChangeHandler, + indexMode: 'standard', + }, + ctx + ); + }); + testBed.component.update(); + + const { + actions: { selectTab }, + find, + } = testBed; + + await selectTab('advanced'); + + // Check that the stored option is selected + expect(find('sourceValueField').prop('value')).toBe('stored'); + }); + + ['logsdb', 'time_series'].forEach((indexMode) => { + it(`defaults to 'synthetic' with ${indexMode} index mode prop on enterprise license`, async () => { + hasEnterpriseLicense = true; + await act(async () => { + testBed = setup( + { + value: { ...defaultMappings, _source: undefined }, + onChange: onChangeHandler, + indexMode, + }, + ctx + ); + }); + testBed.component.update(); + + const { + actions: { selectTab }, + find, + } = testBed; + + await selectTab('advanced'); + + // Check that the synthetic option is selected + expect(find('sourceValueField').prop('value')).toBe('synthetic'); + }); + + it(`defaults to 'standard' with ${indexMode} index mode prop on basic license`, async () => { + hasEnterpriseLicense = false; + await act(async () => { + testBed = setup( + { + value: { ...defaultMappings, _source: undefined }, + onChange: onChangeHandler, + indexMode, + }, + ctx + ); + }); + testBed.component.update(); + + const { + actions: { selectTab }, + find, + } = testBed; + + await selectTab('advanced'); + + // Check that the stored option is selected + expect(find('sourceValueField').prop('value')).toBe('stored'); + }); + }); + }); }); describe('multi-fields support', () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx index e3e32c55aada0..00ce2d02a1baa 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx @@ -5,17 +5,21 @@ * 2.0. */ -import React, { useEffect, useRef, useCallback } from 'react'; +import React, { useEffect, useRef } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { FormData } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; import { useAppContext } from '../../../../app_context'; import { useForm, Form } from '../../shared_imports'; import { GenericObject, MappingsConfiguration } from '../../types'; import { MapperSizePluginId } from '../../constants'; import { useDispatch } from '../../mappings_state_context'; import { DynamicMappingSection } from './dynamic_mapping_section'; -import { SourceFieldSection } from './source_field_section'; +import { + SourceFieldSection, + STORED_SOURCE_OPTION, + SYNTHETIC_SOURCE_OPTION, + DISABLED_SOURCE_OPTION, +} from './source_field_section'; import { MetaFieldSection } from './meta_field_section'; import { RoutingSection } from './routing_section'; import { MapperSizePluginSection } from './mapper_size_plugin_section'; @@ -28,7 +32,14 @@ interface Props { esNodesPlugins: string[]; } -const formSerializer = (formData: GenericObject, sourceFieldMode?: string) => { +interface SerializedSourceField { + enabled?: boolean; + mode?: string; + includes?: string[]; + excludes?: string[]; +} + +export const formSerializer = (formData: GenericObject) => { const { dynamicMapping, sourceField, metaField, _routing, _size, subobjects } = formData; const dynamic = dynamicMapping?.enabled @@ -37,12 +48,30 @@ const formSerializer = (formData: GenericObject, sourceFieldMode?: string) => { ? 'strict' : dynamicMapping?.enabled; + const _source = + sourceField?.option === SYNTHETIC_SOURCE_OPTION + ? { mode: SYNTHETIC_SOURCE_OPTION } + : sourceField?.option === DISABLED_SOURCE_OPTION + ? { enabled: false } + : sourceField?.option === STORED_SOURCE_OPTION + ? { + mode: 'stored', + includes: sourceField?.includes, + excludes: sourceField?.excludes, + } + : sourceField?.includes || sourceField?.excludes + ? { + includes: sourceField?.includes, + excludes: sourceField?.excludes, + } + : undefined; + const serialized = { dynamic, numeric_detection: dynamicMapping?.numeric_detection, date_detection: dynamicMapping?.date_detection, dynamic_date_formats: dynamicMapping?.dynamic_date_formats, - _source: sourceFieldMode ? { mode: sourceFieldMode } : sourceField, + _source: _source as SerializedSourceField, _meta: metaField, _routing, _size, @@ -52,7 +81,7 @@ const formSerializer = (formData: GenericObject, sourceFieldMode?: string) => { return serialized; }; -const formDeserializer = (formData: GenericObject) => { +export const formDeserializer = (formData: GenericObject) => { const { dynamic, /* eslint-disable @typescript-eslint/naming-convention */ @@ -60,11 +89,7 @@ const formDeserializer = (formData: GenericObject) => { date_detection, dynamic_date_formats, /* eslint-enable @typescript-eslint/naming-convention */ - _source: { enabled, includes, excludes } = {} as { - enabled?: boolean; - includes?: string[]; - excludes?: string[]; - }, + _source: { enabled, mode, includes, excludes } = {} as SerializedSourceField, _meta, _routing, // For the Mapper Size plugin @@ -81,7 +106,14 @@ const formDeserializer = (formData: GenericObject) => { dynamic_date_formats, }, sourceField: { - enabled, + option: + mode === 'stored' + ? STORED_SOURCE_OPTION + : mode === 'synthetic' + ? SYNTHETIC_SOURCE_OPTION + : enabled === false + ? DISABLED_SOURCE_OPTION + : undefined, includes, excludes, }, @@ -99,14 +131,9 @@ export const ConfigurationForm = React.memo(({ value, esNodesPlugins }: Props) = const isMounted = useRef(false); - const serializerCallback = useCallback( - (formData: FormData) => formSerializer(formData, value?._source?.mode), - [value?._source?.mode] - ); - const { form } = useForm({ schema: configurationFormSchema, - serializer: serializerCallback, + serializer: formSerializer, deserializer: formDeserializer, defaultValue: value, id: 'configurationForm', @@ -165,7 +192,7 @@ export const ConfigurationForm = React.memo(({ value, esNodesPlugins }: Props) = - {enableMappingsSourceFieldSection && !value?._source?.mode && ( + {enableMappingsSourceFieldSection && ( <> > diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx index a9913b9474b36..ff93e717ce090 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form_schema.tsx @@ -75,12 +75,9 @@ export const configurationFormSchema: FormSchema = { }, }, sourceField: { - enabled: { - label: i18n.translate('xpack.idxMgmt.mappingsEditor.configuration.sourceFieldLabel', { - defaultMessage: 'Enable _source field', - }), - type: FIELD_TYPES.TOGGLE, - defaultValue: true, + option: { + type: FIELD_TYPES.SUPER_SELECT, + defaultValue: 'stored', }, includes: { label: i18n.translate('xpack.idxMgmt.mappingsEditor.configuration.includeSourceFieldsLabel', { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_serialization.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_serialization.test.ts new file mode 100644 index 0000000000000..5bf4ad3b9ee57 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_serialization.test.ts @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { formSerializer, formDeserializer } from './configuration_form'; +import { + STORED_SOURCE_OPTION, + SYNTHETIC_SOURCE_OPTION, + DISABLED_SOURCE_OPTION, +} from './source_field_section'; + +describe('Template serialization', () => { + describe('serialization of _source parameter', () => { + describe('deserializeTemplate()', () => { + test(`correctly deserializes 'stored' mode`, () => { + expect( + formDeserializer({ + _source: { + mode: 'stored', + includes: ['hello'], + excludes: ['world'], + }, + }) + ).toHaveProperty('sourceField', { + option: STORED_SOURCE_OPTION, + includes: ['hello'], + excludes: ['world'], + }); + }); + + test(`correctly deserializes 'enabled' property set to true`, () => { + expect( + formDeserializer({ + _source: { + enabled: true, + includes: ['hello'], + excludes: ['world'], + }, + }) + ).toHaveProperty('sourceField', { + includes: ['hello'], + excludes: ['world'], + }); + }); + + test(`correctly deserializes 'enabled' property set to false`, () => { + expect( + formDeserializer({ + _source: { + enabled: false, + }, + }) + ).toHaveProperty('sourceField', { + option: DISABLED_SOURCE_OPTION, + }); + }); + + test(`correctly deserializes 'synthetic' mode`, () => { + expect( + formDeserializer({ + _source: { + mode: 'synthetic', + }, + }) + ).toHaveProperty('sourceField', { + option: SYNTHETIC_SOURCE_OPTION, + }); + }); + + test(`correctly deserializes undefined mode and enabled properties with includes or excludes fields`, () => { + expect( + formDeserializer({ + _source: { + includes: ['hello'], + excludes: ['world'], + }, + }) + ).toHaveProperty('sourceField', { + includes: ['hello'], + excludes: ['world'], + }); + }); + }); + + describe('serializeTemplate()', () => { + test(`correctly serializes 'stored' option`, () => { + expect( + formSerializer({ + sourceField: { + option: STORED_SOURCE_OPTION, + includes: ['hello'], + excludes: ['world'], + }, + }) + ).toHaveProperty('_source', { + mode: 'stored', + includes: ['hello'], + excludes: ['world'], + }); + }); + + test(`correctly serializes 'disabled' option`, () => { + expect( + formSerializer({ + sourceField: { + option: DISABLED_SOURCE_OPTION, + }, + }) + ).toHaveProperty('_source', { + enabled: false, + }); + }); + + test(`correctly serializes 'synthetic' option`, () => { + expect( + formSerializer({ + sourceField: { + option: SYNTHETIC_SOURCE_OPTION, + }, + }) + ).toHaveProperty('_source', { + mode: 'synthetic', + }); + }); + + test(`correctly serializes undefined option with includes or excludes fields`, () => { + expect( + formSerializer({ + sourceField: { + includes: ['hello'], + excludes: ['world'], + }, + }) + ).toHaveProperty('_source', { + includes: ['hello'], + excludes: ['world'], + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/constants.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/constants.ts new file mode 100644 index 0000000000000..9e4390846fa81 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/constants.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const STORED_SOURCE_OPTION = 'stored'; +export const DISABLED_SOURCE_OPTION = 'disabled'; +export const SYNTHETIC_SOURCE_OPTION = 'synthetic'; + +export type SourceOptionKey = + | typeof STORED_SOURCE_OPTION + | typeof DISABLED_SOURCE_OPTION + | typeof SYNTHETIC_SOURCE_OPTION; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/i18n_texts.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/i18n_texts.ts new file mode 100644 index 0000000000000..447c45b7b099d --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/i18n_texts.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { + STORED_SOURCE_OPTION, + DISABLED_SOURCE_OPTION, + SYNTHETIC_SOURCE_OPTION, + SourceOptionKey, +} from './constants'; + +export const sourceOptionLabels: Record = { + [STORED_SOURCE_OPTION]: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.configuration.storedSourceFieldsLabel', + { + defaultMessage: 'Stored _source', + } + ), + [DISABLED_SOURCE_OPTION]: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.configuration.disabledSourceFieldsLabel', + { + defaultMessage: 'Disabled _source', + } + ), + [SYNTHETIC_SOURCE_OPTION]: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.configuration.syntheticSourceFieldsLabel', + { + defaultMessage: 'Synthetic _source', + } + ), +}; + +export const sourceOptionDescriptions: Record = { + [STORED_SOURCE_OPTION]: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.configuration.storedSourceFieldsDescription', + { + defaultMessage: 'Stores content in _source field for future retrieval', + } + ), + [DISABLED_SOURCE_OPTION]: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.configuration.disabledSourceFieldsDescription', + { + defaultMessage: 'Strongly discouraged, will impact downstream functionality', + } + ), + [SYNTHETIC_SOURCE_OPTION]: i18n.translate( + 'xpack.idxMgmt.mappingsEditor.configuration.syntheticSourceFieldsDescription', + { + defaultMessage: 'Reconstructs source content to save on disk usage', + } + ), +}; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/index.ts index df921a036c909..cb5f2afef6d0b 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/index.ts @@ -6,3 +6,4 @@ */ export { SourceFieldSection } from './source_field_section'; +export * from './constants'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx index 236dfe98119ca..2e8f9fb88f87d 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx @@ -5,18 +5,61 @@ * 2.0. */ -import React from 'react'; +import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { EuiLink, EuiSpacer, EuiComboBox, EuiFormRow, EuiCallOut } from '@elastic/eui'; +import { EuiLink, EuiSpacer, EuiComboBox, EuiFormRow, EuiCallOut, EuiText } from '@elastic/eui'; +import { useMappingsState } from '../../../mappings_state_context'; import { documentationService } from '../../../../../services/documentation'; -import { UseField, FormDataProvider, FormRow, ToggleField } from '../../../shared_imports'; +import { UseField, FormDataProvider, FormRow, SuperSelectField } from '../../../shared_imports'; import { ComboBoxOption } from '../../../types'; +import { sourceOptionLabels, sourceOptionDescriptions } from './i18n_texts'; +import { + STORED_SOURCE_OPTION, + DISABLED_SOURCE_OPTION, + SYNTHETIC_SOURCE_OPTION, + SourceOptionKey, +} from './constants'; export const SourceFieldSection = () => { - const renderWarning = () => ( + const state = useMappingsState(); + + const renderOptionDropdownDisplay = (option: SourceOptionKey) => ( + + {sourceOptionLabels[option]} + + {sourceOptionDescriptions[option]} + + + ); + + const sourceValueOptions = [ + { + value: STORED_SOURCE_OPTION, + inputDisplay: sourceOptionLabels[STORED_SOURCE_OPTION], + dropdownDisplay: renderOptionDropdownDisplay(STORED_SOURCE_OPTION), + 'data-test-subj': 'storedSourceFieldOption', + }, + ]; + + if (state.hasEnterpriseLicense) { + sourceValueOptions.push({ + value: SYNTHETIC_SOURCE_OPTION, + inputDisplay: sourceOptionLabels[SYNTHETIC_SOURCE_OPTION], + dropdownDisplay: renderOptionDropdownDisplay(SYNTHETIC_SOURCE_OPTION), + 'data-test-subj': 'syntheticSourceFieldOption', + }); + } + sourceValueOptions.push({ + value: DISABLED_SOURCE_OPTION, + inputDisplay: sourceOptionLabels[DISABLED_SOURCE_OPTION], + dropdownDisplay: renderOptionDropdownDisplay(DISABLED_SOURCE_OPTION), + 'data-test-subj': 'disabledSourceFieldOption', + }); + + const renderDisableWarning = () => ( { @@ -45,13 +88,13 @@ export const SourceFieldSection = () => { @@ -70,6 +113,44 @@ export const SourceFieldSection = () => { ); + const renderSyntheticWarning = () => ( + + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription2.sourceText', + { + defaultMessage: '_source', + } + )} + + ), + learnMoreLink: ( + + {i18n.translate( + 'xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription2.sourceText', + { + defaultMessage: 'Learn more.', + } + )} + + ), + }} + /> + } + iconType="warning" + color="warning" + /> + ); + const renderFormFields = () => ( @@ -155,21 +236,34 @@ export const SourceFieldSection = () => { }} /> - + > } > - + {(formData) => { - const { - sourceField: { enabled }, - } = formData; + const { sourceField } = formData; - if (enabled === undefined) { + if (sourceField?.option === undefined) { return null; } - return enabled ? renderFormFields() : renderWarning(); + return sourceField?.option === STORED_SOURCE_OPTION + ? renderFormFields() + : sourceField?.option === DISABLED_SOURCE_OPTION + ? renderDisableWarning() + : renderSyntheticWarning(); }} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts index 58b40293f64f2..872c62bc6f7a7 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/lib/utils.test.ts @@ -421,6 +421,7 @@ describe('utils', () => { selectedDataTypes: ['Boolean'], }, inferenceToModelIdMap: {}, + hasEnterpriseLicense: true, mappingViewFields: { byId: {}, rootLevelFields: [], aliases: {}, maxNestedDepth: 0 }, }; test('returns list of matching fields with search term', () => { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx index e1f5306899db3..cc87c3cd614e3 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx @@ -9,6 +9,9 @@ import React, { useMemo, useState, useEffect, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiTabs, EuiTab } from '@elastic/eui'; +import { ILicense } from '@kbn/licensing-plugin/common/types'; +import { useAppContext } from '../../app_context'; +import { IndexMode } from '../../../../common/types/data_streams'; import { DocumentFields, RuntimeFieldsList, @@ -32,6 +35,7 @@ import { DocLinksStart } from './shared_imports'; import { DocumentFieldsHeader } from './components/document_fields/document_fields_header'; import { SearchResult } from './components/document_fields/search_fields'; import { parseMappings } from '../../shared/parse_mappings'; +import { LOGSDB_INDEX_MODE, TIME_SERIES_MODE } from '../../../../common/constants'; type TabName = 'fields' | 'runtimeFields' | 'advanced' | 'templates'; @@ -52,10 +56,14 @@ export interface Props { docLinks: DocLinksStart; /** List of plugins installed in the cluster nodes */ esNodesPlugins: string[]; + indexMode?: IndexMode; } export const MappingsEditor = React.memo( - ({ onChange, value, docLinks, indexSettings, esNodesPlugins }: Props) => { + ({ onChange, value, docLinks, indexSettings, esNodesPlugins, indexMode }: Props) => { + const { + plugins: { licensing }, + } = useAppContext(); const { parsedDefaultValue, multipleMappingsDeclared } = useMemo( () => parseMappings(value), [value] @@ -120,6 +128,40 @@ export const MappingsEditor = React.memo( [dispatch] ); + const [isLicenseCheckComplete, setIsLicenseCheckComplete] = useState(false); + useEffect(() => { + const subscription = licensing?.license$.subscribe((license: ILicense) => { + dispatch({ + type: 'hasEnterpriseLicense.update', + value: license.isActive && license.hasAtLeast('enterprise'), + }); + setIsLicenseCheckComplete(true); + }); + + return () => subscription?.unsubscribe(); + }, [dispatch, licensing]); + + useEffect(() => { + if ( + isLicenseCheckComplete && + !state.configuration.defaultValue._source && + (indexMode === LOGSDB_INDEX_MODE || indexMode === TIME_SERIES_MODE) + ) { + if (state.hasEnterpriseLicense) { + dispatch({ + type: 'configuration.save', + value: { ...state.configuration.defaultValue, _source: { mode: 'synthetic' } } as any, + }); + } + } + }, [ + indexMode, + dispatch, + state.configuration, + state.hasEnterpriseLicense, + isLicenseCheckComplete, + ]); + const tabToContentMap = { fields: ( = ({ childr selectedDataTypes: [], }, inferenceToModelIdMap: {}, + hasEnterpriseLicense: false, mappingViewFields: { byId: {}, rootLevelFields: [], aliases: {}, maxNestedDepth: 0 }, }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts index 626ee0e839a8a..ecb9648c34d00 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/reducer.ts @@ -629,5 +629,11 @@ export const reducer = (state: State, action: Action): State => { inferenceToModelIdMap: action.value.inferenceToModelIdMap, }; } + case 'hasEnterpriseLicense.update': { + return { + ...state, + hasEnterpriseLicense: action.value, + }; + } } }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts index f40fe420eb3be..43b3a7dde3b16 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/types/state.ts @@ -108,6 +108,7 @@ export interface State { }; templates: TemplatesFormState; inferenceToModelIdMap?: InferenceToModelIdMap; + hasEnterpriseLicense: boolean; mappingViewFields: NormalizedFields; // state of the incoming index mappings, separate from the editor state above } @@ -140,6 +141,7 @@ export type Action = | { type: 'fieldsJsonEditor.update'; value: { json: { [key: string]: any }; isValid: boolean } } | { type: 'search:update'; value: string } | { type: 'validity:update'; value: boolean } - | { type: 'filter:update'; value: { selectedOptions: EuiSelectableOption[] } }; + | { type: 'filter:update'; value: { selectedOptions: EuiSelectableOption[] } } + | { type: 'hasEnterpriseLicense.update'; value: boolean }; export type Dispatch = (action: Action) => void; diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx index 62685a05f7ff9..a239971c1bf82 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings.tsx @@ -16,6 +16,7 @@ import { EuiText, } from '@elastic/eui'; +import { IndexMode } from '../../../../../../common/types/data_streams'; import { Forms } from '../../../../../shared_imports'; import { useAppContext } from '../../../../app_context'; import { @@ -33,10 +34,11 @@ interface Props { esNodesPlugins: string[]; defaultValue?: { [key: string]: any }; indexSettings?: IndexSettings; + indexMode?: IndexMode; } export const StepMappings: React.FunctionComponent = React.memo( - ({ defaultValue = {}, onChange, indexSettings, esDocsBase, esNodesPlugins }) => { + ({ defaultValue = {}, onChange, indexSettings, esDocsBase, esNodesPlugins, indexMode }) => { const [mappings, setMappings] = useState(defaultValue); const { docLinks } = useAppContext(); @@ -115,6 +117,7 @@ export const StepMappings: React.FunctionComponent = React.memo( indexSettings={indexSettings} docLinks={docLinks} esNodesPlugins={esNodesPlugins} + indexMode={indexMode} /> diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings_container.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings_container.tsx index 50b763ce0d06a..1b8a6bac2a4d8 100644 --- a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings_container.tsx +++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings_container.tsx @@ -7,6 +7,8 @@ import React from 'react'; +import { WizardContent } from '../../../template_form/template_form'; +import { TemplateDeserialized } from '../../../../../../common'; import { Forms } from '../../../../../shared_imports'; import { useLoadNodesPlugins } from '../../../../services'; import { CommonWizardSteps } from './types'; @@ -14,15 +16,29 @@ import { StepMappings } from './step_mappings'; interface Props { esDocsBase: string; + getTemplateData?: (wizardContent: WizardContent) => TemplateDeserialized; } -export const StepMappingsContainer: React.FunctionComponent = ({ esDocsBase }) => { +export const StepMappingsContainer: React.FunctionComponent = ({ + esDocsBase, + getTemplateData, +}) => { const { defaultValue, updateContent, getSingleContentData } = Forms.useContent< CommonWizardSteps, 'mappings' >('mappings'); const { data: esNodesPlugins } = useLoadNodesPlugins(); + const { getData } = Forms.useMultiContentContext(); + + let indexMode; + if (getTemplateData) { + const wizardContent = getData(); + // Build the current template object, providing the wizard content data + const template = getTemplateData(wizardContent); + indexMode = template?.indexMode; + } + return ( = ({ esDocsBa indexSettings={getSingleContentData('settings')} esDocsBase={esDocsBase} esNodesPlugins={esNodesPlugins ?? []} + indexMode={indexMode} /> ); }; diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx index 53b53a6ebdeee..1f3d2a22874d3 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx @@ -349,7 +349,10 @@ export const TemplateForm = ({ - + diff --git a/x-pack/plugins/index_management/public/application/services/documentation.ts b/x-pack/plugins/index_management/public/application/services/documentation.ts index 58aba69351883..62b7defd78db1 100644 --- a/x-pack/plugins/index_management/public/application/services/documentation.ts +++ b/x-pack/plugins/index_management/public/application/services/documentation.ts @@ -55,6 +55,7 @@ class DocumentationService { private mappingSimilarity: string = ''; private mappingSourceFields: string = ''; private mappingSourceFieldsDisable: string = ''; + private mappingSyntheticSourceFields: string = ''; private mappingStore: string = ''; private mappingSubobjects: string = ''; private mappingTermVector: string = ''; @@ -115,6 +116,7 @@ class DocumentationService { this.mappingSimilarity = links.elasticsearch.mappingSimilarity; this.mappingSourceFields = links.elasticsearch.mappingSourceFields; this.mappingSourceFieldsDisable = links.elasticsearch.mappingSourceFieldsDisable; + this.mappingSyntheticSourceFields = links.elasticsearch.mappingSyntheticSourceFields; this.mappingStore = links.elasticsearch.mappingStore; this.mappingSubobjects = links.elasticsearch.mappingSubobjects; this.mappingTermVector = links.elasticsearch.mappingTermVector; @@ -215,6 +217,10 @@ class DocumentationService { return this.mappingSourceFieldsDisable; } + public getMappingSyntheticSourceFieldLink() { + return this.mappingSyntheticSourceFields; + } + public getNullValueLink() { return this.mappingNullValue; } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 88a3e85ece298..8260e5c53bd8e 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -22939,7 +22939,6 @@ "xpack.idxMgmt.mappingsEditor.configuration.numericFieldLabel": "Mapper les chaînes numériques en tant que nombres", "xpack.idxMgmt.mappingsEditor.configuration.routingLabel": "Demander une valeur _routing pour les opérations CRUD", "xpack.idxMgmt.mappingsEditor.configuration.sizeLabel": "Indexer la taille du champ _source en octets", - "xpack.idxMgmt.mappingsEditor.configuration.sourceFieldLabel": "Activer le champ _source", "xpack.idxMgmt.mappingsEditor.configuration.sourceFieldPathComboBoxHelpText": "Accepte un chemin d'accès au champ, y compris les caractères génériques.", "xpack.idxMgmt.mappingsEditor.configuration.subobjectsLabel": "Autoriser les objets à contenir d'autres sous-objets", "xpack.idxMgmt.mappingsEditor.configuration.throwErrorsForUnmappedFieldsLabel": "Lever une exception lorsqu'un document contient un champ non mappé", @@ -23082,7 +23081,6 @@ "xpack.idxMgmt.mappingsEditor.dimsFieldLabel": "Dimensions", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription1": "La désactivation de {source} réduit la surcharge de stockage dans l'index, mais cela a un coût. Cette action désactive également des fonctionnalités essentielles, comme la capacité à réindexer ou à déboguer les requêtes en affichant le document d'origine.", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription1.sourceText": "_source", - "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription2": "Découvrez-en plus sur les alternatives à la désactivation du champ {source}.", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription2.sourceText": "_source", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutTitle": "Faites preuve de prudence lorsque vous désactivez le champ _source", "xpack.idxMgmt.mappingsEditor.documentFields.searchFieldsAriaLabel": "Rechercher dans les champs mappés", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index aa7f66dde6817..38185735b9d3b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -22911,7 +22911,6 @@ "xpack.idxMgmt.mappingsEditor.configuration.numericFieldLabel": "数字の文字列の数値としてのマッピング", "xpack.idxMgmt.mappingsEditor.configuration.routingLabel": "CRUD操作のためのRequire _routing値", "xpack.idxMgmt.mappingsEditor.configuration.sizeLabel": "_sourceフィールドサイズ(バイト)にインデックスを作成", - "xpack.idxMgmt.mappingsEditor.configuration.sourceFieldLabel": "_sourceフィールドの有効化", "xpack.idxMgmt.mappingsEditor.configuration.sourceFieldPathComboBoxHelpText": "ワイルドカードを含め、フィールドへのパスを受け入れます。", "xpack.idxMgmt.mappingsEditor.configuration.subobjectsLabel": "オブジェクトがさらにサブオブジェクトを保持することを許可", "xpack.idxMgmt.mappingsEditor.configuration.throwErrorsForUnmappedFieldsLabel": "ドキュメントがマッピングされていないフィールドを含む場合に例外を選択する", @@ -23054,7 +23053,6 @@ "xpack.idxMgmt.mappingsEditor.dimsFieldLabel": "次元", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription1": "{source}を無効にすることで、インデックス内のストレージオーバーヘッドが削減されますが、これにはコストがかかります。これはまた、元のドキュメントを表示して、再インデックスやクエリーのデバッグといった重要な機能を無効にします。", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription1.sourceText": "_source", - "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription2": "{source}フィールドを無効にするための代替方法の詳細", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription2.sourceText": "_source", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutTitle": "_source fieldを無効にする際は慎重に行う", "xpack.idxMgmt.mappingsEditor.documentFields.searchFieldsAriaLabel": "マッピングされたフィールドの検索", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index beeaabe1752f3..03662564cefe5 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -22963,7 +22963,6 @@ "xpack.idxMgmt.mappingsEditor.configuration.numericFieldLabel": "将数值字符串映射为数字", "xpack.idxMgmt.mappingsEditor.configuration.routingLabel": "CRUD 操作需要 _routing 值", "xpack.idxMgmt.mappingsEditor.configuration.sizeLabel": "索引 _source 字段大小(字节)", - "xpack.idxMgmt.mappingsEditor.configuration.sourceFieldLabel": "启用 _source 字段", "xpack.idxMgmt.mappingsEditor.configuration.sourceFieldPathComboBoxHelpText": "接受字段的路径,包括通配符。", "xpack.idxMgmt.mappingsEditor.configuration.subobjectsLabel": "允许对象存放更多子对象", "xpack.idxMgmt.mappingsEditor.configuration.throwErrorsForUnmappedFieldsLabel": "文档包含未映射字段时引发异常", @@ -23106,7 +23105,6 @@ "xpack.idxMgmt.mappingsEditor.dimsFieldLabel": "维度数", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription1": "禁用 {source} 可降低索引内的存储开销,这有一定的代价。其还禁用重要的功能,如通过查看原始文档来重新索引或调试查询的功能。", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription1.sourceText": "_source", - "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription2": "详细了解禁用 {source} 字段的备选方式。", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutDescription2.sourceText": "_source", "xpack.idxMgmt.mappingsEditor.disabledSourceFieldCallOutTitle": "禁用 _source 字段时要十分谨慎", "xpack.idxMgmt.mappingsEditor.documentFields.searchFieldsAriaLabel": "搜索映射的字段",
{sourceOptionDescriptions[option]}
@@ -45,13 +88,13 @@ export const SourceFieldSection = () => { @@ -70,6 +113,44 @@ export const SourceFieldSection = () => {
@@ -70,6 +113,44 @@ export const SourceFieldSection = () => {