mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
parent
7c575de914
commit
0b85d28b2b
14 changed files with 468 additions and 65 deletions
|
@ -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 { setup as mappingsEditorSetup } from './mappings_editor.helpers';
|
||||
|
||||
export {
|
||||
nextTick,
|
||||
getRandomString,
|
||||
findTestSubject,
|
||||
TestBed,
|
||||
} from '../../../../../../../../../../test_utils';
|
||||
|
||||
export const componentHelpers = {
|
||||
mappingsEditor: { setup: mappingsEditorSetup },
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { registerTestBed } from '../../../../../../../../../../test_utils';
|
||||
import { MappingsEditor } from '../../../mappings_editor';
|
||||
|
||||
export const setup = (props: any) =>
|
||||
registerTestBed(MappingsEditor, {
|
||||
memoryRouter: {
|
||||
wrapComponent: false,
|
||||
},
|
||||
defaultProps: props,
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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 { componentHelpers } from './helpers';
|
||||
|
||||
const { setup } = componentHelpers.mappingsEditor;
|
||||
const mockOnUpdate = () => undefined;
|
||||
|
||||
describe('<MappingsEditor />', () => {
|
||||
describe('multiple mappings detection', () => {
|
||||
test('should show a warning when multiple mappings are detected', async () => {
|
||||
const defaultValue = {
|
||||
type1: {
|
||||
properties: {
|
||||
name1: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
type2: {
|
||||
properties: {
|
||||
name2: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue })();
|
||||
const { exists } = testBed;
|
||||
|
||||
expect(exists('mappingsEditor')).toBe(true);
|
||||
expect(exists('mappingTypesDetectedCallout')).toBe(true);
|
||||
expect(exists('documentFields')).toBe(false);
|
||||
});
|
||||
|
||||
test('should not show a warning when mappings a single-type', async () => {
|
||||
const defaultValue = {
|
||||
properties: {
|
||||
name1: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
};
|
||||
const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue })();
|
||||
const { exists } = testBed;
|
||||
|
||||
expect(exists('mappingsEditor')).toBe(true);
|
||||
expect(exists('mappingTypesDetectedCallout')).toBe(false);
|
||||
expect(exists('documentFields')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -48,7 +48,7 @@ export const DocumentFields = React.memo(() => {
|
|||
const searchTerm = search.term.trim();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-test-subj="documentFields">
|
||||
<DocumentFieldsHeader searchValue={search.term} onSearchChange={onSearchChange} />
|
||||
<EuiSpacer size="m" />
|
||||
{searchTerm !== '' ? (
|
||||
|
@ -57,6 +57,6 @@ export const DocumentFields = React.memo(() => {
|
|||
editor
|
||||
)}
|
||||
{renderEditField()}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -9,3 +9,5 @@ export * from './configuration_form';
|
|||
export * from './document_fields';
|
||||
|
||||
export * from './templates_form';
|
||||
|
||||
export * from './multiple_mappings_warning';
|
||||
|
|
|
@ -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 { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiCallOut, EuiLink } from '@elastic/eui';
|
||||
|
||||
import { documentationService } from '../../../services/documentation';
|
||||
|
||||
export const MultipleMappingsWarning = () => (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.idxMgmt.mappingsEditor.mappingTypesDetectedCallOutTitle', {
|
||||
defaultMessage: 'Mapping types detected',
|
||||
})}
|
||||
iconType="alert"
|
||||
color="warning"
|
||||
data-test-subj="mappingTypesDetectedCallout"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.idxMgmt.mappingsEditor.mappingTypesDetectedCallOutDescription"
|
||||
defaultMessage="The mappings for this template uses types, which have been removed. {docsLink}"
|
||||
values={{
|
||||
docsLink: (
|
||||
<EuiLink href={documentationService.getAlternativeToMappingTypesLink()} target="_blank">
|
||||
{i18n.translate(
|
||||
'xpack.idxMgmt.mappingsEditor.mappingTypesDetectedCallOutDocumentationLink',
|
||||
{
|
||||
defaultMessage: 'Consider these alternatives to mapping types.',
|
||||
}
|
||||
)}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
);
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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 { extractMappingsDefinition } from './extract_mappings_definition';
|
||||
|
||||
describe('extractMappingsDefinition', () => {
|
||||
test('should detect that the mappings has multiple types and return null', () => {
|
||||
const mappings = {
|
||||
type1: {
|
||||
properties: {
|
||||
name1: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
type2: {
|
||||
properties: {
|
||||
name2: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(extractMappingsDefinition(mappings)).toBe(null);
|
||||
});
|
||||
|
||||
test('should detect that the mappings has multiple types even when one of the type has not defined any "properties"', () => {
|
||||
const mappings = {
|
||||
type1: {
|
||||
_source: {
|
||||
excludes: [],
|
||||
includes: [],
|
||||
enabled: true,
|
||||
},
|
||||
_routing: {
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
type2: {
|
||||
properties: {
|
||||
name2: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(extractMappingsDefinition(mappings)).toBe(null);
|
||||
});
|
||||
|
||||
test('should detect that one of the mapping type is invalid and filter it out', () => {
|
||||
const mappings = {
|
||||
type1: {
|
||||
invalidSetting: {
|
||||
excludes: [],
|
||||
includes: [],
|
||||
enabled: true,
|
||||
},
|
||||
_routing: {
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
type2: {
|
||||
properties: {
|
||||
name2: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(extractMappingsDefinition(mappings)).toBe(mappings.type2);
|
||||
});
|
||||
|
||||
test('should detect that the mappings has one type and return its mapping definition', () => {
|
||||
const mappings = {
|
||||
myType: {
|
||||
_source: {
|
||||
excludes: [],
|
||||
includes: [],
|
||||
enabled: true,
|
||||
},
|
||||
_meta: {},
|
||||
_routing: {
|
||||
required: false,
|
||||
},
|
||||
dynamic: true,
|
||||
properties: {
|
||||
title: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(extractMappingsDefinition(mappings)).toBe(mappings.myType);
|
||||
});
|
||||
|
||||
test('should detect that the mappings has one type at root level', () => {
|
||||
const mappings = {
|
||||
_source: {
|
||||
excludes: [],
|
||||
includes: [],
|
||||
enabled: true,
|
||||
},
|
||||
_meta: {},
|
||||
_routing: {
|
||||
required: false,
|
||||
},
|
||||
dynamic: true,
|
||||
numeric_detection: false,
|
||||
date_detection: true,
|
||||
dynamic_date_formats: ['strict_date_optional_time'],
|
||||
dynamic_templates: [],
|
||||
properties: {
|
||||
title: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(extractMappingsDefinition(mappings)).toBe(mappings);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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 { isPlainObject } from 'lodash';
|
||||
|
||||
import { GenericObject } from '../types';
|
||||
import {
|
||||
validateMappingsConfiguration,
|
||||
mappingsConfigurationSchemaKeys,
|
||||
} from './mappings_validator';
|
||||
|
||||
const ALLOWED_PARAMETERS = [...mappingsConfigurationSchemaKeys, 'dynamic_templates', 'properties'];
|
||||
|
||||
const isMappingDefinition = (obj: GenericObject): boolean => {
|
||||
const areAllKeysValid = Object.keys(obj).every(key => ALLOWED_PARAMETERS.includes(key));
|
||||
|
||||
if (!areAllKeysValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { properties, dynamic_templates: dynamicTemplates, ...mappingsConfiguration } = obj;
|
||||
|
||||
const { errors } = validateMappingsConfiguration(mappingsConfiguration);
|
||||
const isConfigurationValid = errors.length === 0;
|
||||
const isPropertiesValid = properties === undefined || isPlainObject(properties);
|
||||
const isDynamicTemplatesValid = dynamicTemplates === undefined || Array.isArray(dynamicTemplates);
|
||||
|
||||
// If the configuration, the properties and the dynamic templates are valid
|
||||
// we can assume that the mapping is declared at root level (no types)
|
||||
return isConfigurationValid && isPropertiesValid && isDynamicTemplatesValid;
|
||||
};
|
||||
|
||||
/**
|
||||
* 5.x index templates can be created with multiple types.
|
||||
* e.g.
|
||||
```
|
||||
const mappings = {
|
||||
type1: {
|
||||
properties: {
|
||||
name1: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
type2: {
|
||||
properties: {
|
||||
name2: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
* A mappings can also be declared under an explicit "_doc" property.
|
||||
```
|
||||
const mappings = {
|
||||
_doc: {
|
||||
_source: {
|
||||
"enabled": false
|
||||
},
|
||||
properties: {
|
||||
name1: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
* This helpers parse the mappings provided an removes any possible mapping "type" declared
|
||||
*
|
||||
* @param mappings The mappings object to validate
|
||||
*/
|
||||
export const extractMappingsDefinition = (mappings: GenericObject = {}): GenericObject | null => {
|
||||
if (isMappingDefinition(mappings)) {
|
||||
// No need to go any further
|
||||
return mappings;
|
||||
}
|
||||
|
||||
// At this point there must be one or more type mappings
|
||||
const typedMappings = Object.values(mappings).reduce((acc: GenericObject[], value) => {
|
||||
if (isMappingDefinition(value)) {
|
||||
acc.push(value as GenericObject);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// If there are no typed mappings found this means that one of the type must did not pass
|
||||
// the "isMappingDefinition()" validation.
|
||||
// In theory this should never happen but let's make sure the UI does not try to load an invalid mapping
|
||||
if (typedMappings.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If there's only one mapping type then we can consume it as if the type doesn't exist.
|
||||
if (typedMappings.length === 1) {
|
||||
return typedMappings[0];
|
||||
}
|
||||
|
||||
// If there's more than one mapping type, then the mappings object isn't usable.
|
||||
return null;
|
||||
};
|
|
@ -13,3 +13,5 @@ export * from './validators';
|
|||
export * from './mappings_validator';
|
||||
|
||||
export * from './search_fields';
|
||||
|
||||
export * from './extract_mappings_definition';
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { isPlainObject } from 'lodash';
|
||||
|
||||
import { validateMappings, validateProperties, isObject } from './mappings_validator';
|
||||
import { validateMappings, validateProperties } from './mappings_validator';
|
||||
|
||||
describe('Mappings configuration validator', () => {
|
||||
it('should convert non object to empty object', () => {
|
||||
|
@ -12,7 +13,7 @@ describe('Mappings configuration validator', () => {
|
|||
|
||||
tests.forEach(testValue => {
|
||||
const { value, errors } = validateMappings(testValue as any);
|
||||
expect(isObject(value)).toBe(true);
|
||||
expect(isPlainObject(value)).toBe(true);
|
||||
expect(errors).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
@ -76,7 +77,7 @@ describe('Properties validator', () => {
|
|||
|
||||
tests.forEach(testValue => {
|
||||
const { value, errors } = validateProperties(testValue as any);
|
||||
expect(isObject(value)).toBe(true);
|
||||
expect(isPlainObject(value)).toBe(true);
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { pick } from 'lodash';
|
||||
import { pick, isPlainObject } from 'lodash';
|
||||
import * as t from 'io-ts';
|
||||
import { ordString } from 'fp-ts/lib/Ord';
|
||||
import { toArray } from 'fp-ts/lib/Set';
|
||||
import { isLeft, isRight } from 'fp-ts/lib/Either';
|
||||
|
||||
import { errorReporter } from './error_reporter';
|
||||
import { ALL_DATA_TYPES, PARAMETERS_DEFINITION } from '../constants';
|
||||
import { FieldMeta } from '../types';
|
||||
import { getFieldMeta } from '../lib';
|
||||
import { getFieldMeta } from './utils';
|
||||
|
||||
const ALLOWED_FIELD_PROPERTIES = [
|
||||
...Object.keys(PARAMETERS_DEFINITION),
|
||||
|
@ -49,8 +50,6 @@ interface GenericObject {
|
|||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const isObject = (obj: any) => obj != null && obj.constructor.name === 'Object';
|
||||
|
||||
const validateFieldType = (type: any): boolean => {
|
||||
if (typeof type !== 'string') {
|
||||
return false;
|
||||
|
@ -72,7 +71,7 @@ const validateParameter = (parameter: string, value: any): boolean => {
|
|||
}
|
||||
|
||||
if (parameter === 'properties' || parameter === 'fields') {
|
||||
return isObject(value);
|
||||
return isPlainObject(value);
|
||||
}
|
||||
|
||||
const parameterSchema = (PARAMETERS_DEFINITION as any)[parameter]!.schema;
|
||||
|
@ -100,7 +99,7 @@ const stripUnknownOrInvalidParameter = (field: GenericObject): FieldValidatorRes
|
|||
|
||||
const parseField = (field: any): FieldValidatorResponse & { meta?: FieldMeta } => {
|
||||
// Sanitize the input to make sure we are working with an object
|
||||
if (!isObject(field)) {
|
||||
if (!isPlainObject(field)) {
|
||||
return { parametersRemoved: [] };
|
||||
}
|
||||
// Make sure the field "type" is valid
|
||||
|
@ -186,7 +185,7 @@ const parseFields = (
|
|||
*/
|
||||
export const validateProperties = (properties = {}): PropertiesValidatorResponse => {
|
||||
// Sanitize the input to make sure we are working with an object
|
||||
if (!isObject(properties)) {
|
||||
if (!isPlainObject(properties)) {
|
||||
return { value: {}, errors: [] };
|
||||
}
|
||||
|
||||
|
@ -213,9 +212,9 @@ export const mappingsConfigurationSchema = t.partial({
|
|||
}),
|
||||
});
|
||||
|
||||
const mappingsConfigurationSchemaKeys = Object.keys(mappingsConfigurationSchema.props);
|
||||
export const mappingsConfigurationSchemaKeys = Object.keys(mappingsConfigurationSchema.props);
|
||||
|
||||
const validateMappingsConfiguration = (
|
||||
export const validateMappingsConfiguration = (
|
||||
mappingsConfiguration: any
|
||||
): { value: any; errors: MappingsValidationError[] } => {
|
||||
// Set to keep track of invalid configuration parameters.
|
||||
|
@ -249,7 +248,7 @@ const validateMappingsConfiguration = (
|
|||
};
|
||||
|
||||
export const validateMappings = (mappings: any = {}): MappingsValidatorResponse => {
|
||||
if (!isObject(mappings)) {
|
||||
if (!isPlainObject(mappings)) {
|
||||
return { value: {} };
|
||||
}
|
||||
|
||||
|
|
|
@ -4,14 +4,20 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer, EuiTabs, EuiTab } from '@elastic/eui';
|
||||
|
||||
import { ConfigurationForm, DocumentFields, TemplatesForm } from './components';
|
||||
import {
|
||||
ConfigurationForm,
|
||||
DocumentFields,
|
||||
TemplatesForm,
|
||||
MultipleMappingsWarning,
|
||||
} from './components';
|
||||
import { IndexSettings } from './types';
|
||||
import { extractMappingsDefinition } from './lib';
|
||||
import { State } from './reducer';
|
||||
import { MappingsState, Props as MappingsStateProps } from './mappings_state';
|
||||
import { MappingsState, Props as MappingsStateProps, Types } from './mappings_state';
|
||||
import { IndexSettingsProvider } from './index_settings_context';
|
||||
|
||||
interface Props {
|
||||
|
@ -25,7 +31,13 @@ type TabName = 'fields' | 'advanced' | 'templates';
|
|||
export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSettings }: Props) => {
|
||||
const [selectedTab, selectTab] = useState<TabName>('fields');
|
||||
|
||||
const parsedDefaultValue = useMemo(() => {
|
||||
const { parsedDefaultValue, multipleMappingsDeclared } = useMemo(() => {
|
||||
const mappingsDefinition = extractMappingsDefinition(defaultValue);
|
||||
|
||||
if (mappingsDefinition === null) {
|
||||
return { multipleMappingsDeclared: true };
|
||||
}
|
||||
|
||||
const {
|
||||
_source = {},
|
||||
_meta = {},
|
||||
|
@ -36,9 +48,9 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting
|
|||
dynamic_date_formats,
|
||||
properties = {},
|
||||
dynamic_templates,
|
||||
} = defaultValue ?? {};
|
||||
} = mappingsDefinition;
|
||||
|
||||
return {
|
||||
const parsed = {
|
||||
configuration: {
|
||||
_source,
|
||||
_meta,
|
||||
|
@ -53,8 +65,21 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting
|
|||
dynamic_templates,
|
||||
},
|
||||
};
|
||||
|
||||
return { parsedDefaultValue: parsed, multipleMappingsDeclared: false };
|
||||
}, [defaultValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (multipleMappingsDeclared) {
|
||||
// We set the data getter here as the user won't be able to make any changes
|
||||
onUpdate({
|
||||
getData: () => defaultValue! as Types['Mappings'],
|
||||
validate: () => Promise.resolve(true),
|
||||
isValid: true,
|
||||
});
|
||||
}
|
||||
}, [multipleMappingsDeclared]);
|
||||
|
||||
const changeTab = async (tab: TabName, state: State) => {
|
||||
if (selectedTab === 'advanced') {
|
||||
// When we navigate away we need to submit the form to validate if there are any errors.
|
||||
|
@ -63,7 +88,6 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting
|
|||
if (!isConfigurationFormValid) {
|
||||
/**
|
||||
* Don't navigate away from the tab if there are errors in the form.
|
||||
* For now there is no need to display a CallOut as the form can never be invalid.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
@ -79,51 +103,57 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting
|
|||
};
|
||||
|
||||
return (
|
||||
<IndexSettingsProvider indexSettings={indexSettings}>
|
||||
<MappingsState onUpdate={onUpdate} defaultValue={parsedDefaultValue}>
|
||||
{({ state }) => {
|
||||
const tabToContentMap = {
|
||||
fields: <DocumentFields />,
|
||||
templates: <TemplatesForm defaultValue={state.templates.defaultValue} />,
|
||||
advanced: <ConfigurationForm defaultValue={state.configuration.defaultValue} />,
|
||||
};
|
||||
<div data-test-subj="mappingsEditor">
|
||||
{multipleMappingsDeclared ? (
|
||||
<MultipleMappingsWarning />
|
||||
) : (
|
||||
<IndexSettingsProvider indexSettings={indexSettings}>
|
||||
<MappingsState onUpdate={onUpdate} defaultValue={parsedDefaultValue!}>
|
||||
{({ state }) => {
|
||||
const tabToContentMap = {
|
||||
fields: <DocumentFields />,
|
||||
templates: <TemplatesForm defaultValue={state.templates.defaultValue} />,
|
||||
advanced: <ConfigurationForm defaultValue={state.configuration.defaultValue} />,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mappingsEditor">
|
||||
<EuiTabs>
|
||||
<EuiTab
|
||||
onClick={() => changeTab('fields', state)}
|
||||
isSelected={selectedTab === 'fields'}
|
||||
>
|
||||
{i18n.translate('xpack.idxMgmt.mappingsEditor.fieldsTabLabel', {
|
||||
defaultMessage: 'Mapped fields',
|
||||
})}
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
onClick={() => changeTab('templates', state)}
|
||||
isSelected={selectedTab === 'templates'}
|
||||
>
|
||||
{i18n.translate('xpack.idxMgmt.mappingsEditor.templatesTabLabel', {
|
||||
defaultMessage: 'Dynamic templates',
|
||||
})}
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
onClick={() => changeTab('advanced', state)}
|
||||
isSelected={selectedTab === 'advanced'}
|
||||
>
|
||||
{i18n.translate('xpack.idxMgmt.mappingsEditor.advancedTabLabel', {
|
||||
defaultMessage: 'Advanced options',
|
||||
})}
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
return (
|
||||
<div className="mappingsEditor">
|
||||
<EuiTabs>
|
||||
<EuiTab
|
||||
onClick={() => changeTab('fields', state)}
|
||||
isSelected={selectedTab === 'fields'}
|
||||
>
|
||||
{i18n.translate('xpack.idxMgmt.mappingsEditor.fieldsTabLabel', {
|
||||
defaultMessage: 'Mapped fields',
|
||||
})}
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
onClick={() => changeTab('templates', state)}
|
||||
isSelected={selectedTab === 'templates'}
|
||||
>
|
||||
{i18n.translate('xpack.idxMgmt.mappingsEditor.templatesTabLabel', {
|
||||
defaultMessage: 'Dynamic templates',
|
||||
})}
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
onClick={() => changeTab('advanced', state)}
|
||||
isSelected={selectedTab === 'advanced'}
|
||||
>
|
||||
{i18n.translate('xpack.idxMgmt.mappingsEditor.advancedTabLabel', {
|
||||
defaultMessage: 'Advanced options',
|
||||
})}
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{tabToContentMap[selectedTab]}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</MappingsState>
|
||||
</IndexSettingsProvider>
|
||||
{tabToContentMap[selectedTab]}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</MappingsState>
|
||||
</IndexSettingsProvider>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -287,3 +287,7 @@ export interface SearchMetadata {
|
|||
*/
|
||||
stringMatch: string | null;
|
||||
}
|
||||
|
||||
export interface GenericObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
|
@ -181,6 +181,10 @@ class DocumentationService {
|
|||
return `${this.esDocsBase}/index-options.html`;
|
||||
}
|
||||
|
||||
public getAlternativeToMappingTypesLink() {
|
||||
return `${this.esDocsBase}/removal-of-types.html#_alternatives_to_mapping_types`;
|
||||
}
|
||||
|
||||
public getJoinMultiLevelsPerformanceLink() {
|
||||
return `${this.esDocsBase}/parent-join.html#_parent_join_and_performance`;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue