mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 10:23:14 -04:00
This commit is contained in:
parent
ff7aa450d9
commit
a0529f7074
18 changed files with 602 additions and 202 deletions
|
@ -13,4 +13,4 @@ export const setup = (props: any) =>
|
||||||
wrapComponent: false,
|
wrapComponent: false,
|
||||||
},
|
},
|
||||||
defaultProps: props,
|
defaultProps: props,
|
||||||
});
|
})();
|
||||||
|
|
|
@ -28,7 +28,7 @@ describe('<MappingsEditor />', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue })();
|
const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue });
|
||||||
const { exists } = testBed;
|
const { exists } = testBed;
|
||||||
|
|
||||||
expect(exists('mappingsEditor')).toBe(true);
|
expect(exists('mappingsEditor')).toBe(true);
|
||||||
|
@ -44,7 +44,7 @@ describe('<MappingsEditor />', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue })();
|
const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue });
|
||||||
const { exists } = testBed;
|
const { exists } = testBed;
|
||||||
|
|
||||||
expect(exists('mappingsEditor')).toBe(true);
|
expect(exists('mappingsEditor')).toBe(true);
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
/*
|
||||||
|
* 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 { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
|
jest.mock('@elastic/eui', () => ({
|
||||||
|
...jest.requireActual('@elastic/eui'),
|
||||||
|
// Mocking EuiCodeEditor, which uses React Ace under the hood
|
||||||
|
EuiCodeEditor: (props: any) => (
|
||||||
|
<input
|
||||||
|
data-test-subj="mockCodeEditor"
|
||||||
|
onChange={(syntheticEvent: any) => {
|
||||||
|
props.onChange(syntheticEvent.jsonString);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { registerTestBed, nextTick, TestBed } from '../../../../../../../../../test_utils';
|
||||||
|
import { LoadMappingsProvider } from './load_mappings_provider';
|
||||||
|
|
||||||
|
const ComponentToTest = ({ onJson }: { onJson: () => void }) => (
|
||||||
|
<LoadMappingsProvider onJson={onJson}>
|
||||||
|
{openModal => (
|
||||||
|
<button onClick={openModal} data-test-subj="load-json-button">
|
||||||
|
Load JSON
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</LoadMappingsProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const setup = (props: any) =>
|
||||||
|
registerTestBed(ComponentToTest, {
|
||||||
|
memoryRouter: { wrapComponent: false },
|
||||||
|
defaultProps: props,
|
||||||
|
})();
|
||||||
|
|
||||||
|
const openModalWithJsonContent = ({ find, component }: TestBed) => async (json: any) => {
|
||||||
|
find('load-json-button').simulate('click');
|
||||||
|
component.update();
|
||||||
|
|
||||||
|
// Set the mappings to load
|
||||||
|
// @ts-ignore
|
||||||
|
await act(async () => {
|
||||||
|
find('mockCodeEditor').simulate('change', {
|
||||||
|
jsonString: JSON.stringify(json),
|
||||||
|
});
|
||||||
|
await nextTick(300); // There is a debounce in the JsonEditor that we need to wait for
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<LoadMappingsProvider />', () => {
|
||||||
|
test('it should forward valid mapping definition', async () => {
|
||||||
|
const mappingsToLoad = {
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const onJson = jest.fn();
|
||||||
|
const testBed = await setup({ onJson });
|
||||||
|
|
||||||
|
// Open the modal and add the JSON
|
||||||
|
await openModalWithJsonContent(testBed)(mappingsToLoad);
|
||||||
|
|
||||||
|
// Confirm
|
||||||
|
testBed.find('confirmModalConfirmButton').simulate('click');
|
||||||
|
|
||||||
|
const [jsonReturned] = onJson.mock.calls[0];
|
||||||
|
expect(jsonReturned).toEqual({ ...mappingsToLoad, dynamic_templates: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should detect custom single-type mappings and return it', async () => {
|
||||||
|
const mappingsToLoadOneType = {
|
||||||
|
myCustomType: {
|
||||||
|
_source: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dynamic_templates: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const onJson = jest.fn();
|
||||||
|
const testBed = await setup({ onJson });
|
||||||
|
|
||||||
|
await openModalWithJsonContent(testBed)(mappingsToLoadOneType);
|
||||||
|
|
||||||
|
// Confirm
|
||||||
|
testBed.find('confirmModalConfirmButton').simulate('click');
|
||||||
|
|
||||||
|
const [jsonReturned] = onJson.mock.calls[0];
|
||||||
|
expect(jsonReturned).toEqual(mappingsToLoadOneType);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should detect multi-type mappings and return raw without validating', async () => {
|
||||||
|
const mappingsToLoadMultiType = {
|
||||||
|
myCustomType1: {
|
||||||
|
wrongParameter: 'wont be validated neither stripped out',
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'wrongType',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dynamic_templates: [],
|
||||||
|
},
|
||||||
|
myCustomType2: {
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dynamic_templates: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const onJson = jest.fn();
|
||||||
|
const testBed = await setup({ onJson });
|
||||||
|
|
||||||
|
await openModalWithJsonContent(testBed)(mappingsToLoadMultiType);
|
||||||
|
|
||||||
|
// Confirm
|
||||||
|
testBed.find('confirmModalConfirmButton').simulate('click');
|
||||||
|
|
||||||
|
const [jsonReturned] = onJson.mock.calls[0];
|
||||||
|
expect(jsonReturned).toEqual(mappingsToLoadMultiType);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should detect single-type mappings under a valid mappings definition parameter', async () => {
|
||||||
|
const mappingsToLoadOneType = {
|
||||||
|
// Custom type name _is_ a valid mappings definition parameter
|
||||||
|
_source: {
|
||||||
|
_source: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dynamic_templates: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const onJson = jest.fn();
|
||||||
|
const testBed = await setup({ onJson });
|
||||||
|
|
||||||
|
await openModalWithJsonContent(testBed)(mappingsToLoadOneType);
|
||||||
|
|
||||||
|
// Confirm
|
||||||
|
testBed.find('confirmModalConfirmButton').simulate('click');
|
||||||
|
|
||||||
|
const [jsonReturned] = onJson.mock.calls[0];
|
||||||
|
expect(jsonReturned).toEqual(mappingsToLoadOneType);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should treat "properties" as properties definition and **not** as a cutom type', async () => {
|
||||||
|
const mappingsToLoadOneType = {
|
||||||
|
// Custom type name _is_ a valid mappings definition parameter
|
||||||
|
properties: {
|
||||||
|
_source: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dynamic_templates: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const onJson = jest.fn();
|
||||||
|
const testBed = await setup({ onJson });
|
||||||
|
|
||||||
|
await openModalWithJsonContent(testBed)(mappingsToLoadOneType);
|
||||||
|
|
||||||
|
// Confirm
|
||||||
|
testBed.find('confirmModalConfirmButton').simulate('click');
|
||||||
|
|
||||||
|
// Make sure our handler hasn't been called
|
||||||
|
expect(onJson.mock.calls.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef } from 'react';
|
||||||
|
import { isPlainObject } from 'lodash';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { FormattedMessage } from '@kbn/i18n/react';
|
import { FormattedMessage } from '@kbn/i18n/react';
|
||||||
import {
|
import {
|
||||||
|
@ -17,7 +18,7 @@ import {
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
import { JsonEditor, OnJsonEditorUpdateHandler } from '../../shared_imports';
|
import { JsonEditor, OnJsonEditorUpdateHandler } from '../../shared_imports';
|
||||||
import { validateMappings, MappingsValidationError } from '../../lib';
|
import { validateMappings, MappingsValidationError, VALID_MAPPINGS_PARAMETERS } from '../../lib';
|
||||||
|
|
||||||
const MAX_ERRORS_TO_DISPLAY = 1;
|
const MAX_ERRORS_TO_DISPLAY = 1;
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ type OpenJsonModalFunc = () => void;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onJson(json: { [key: string]: any }): void;
|
onJson(json: { [key: string]: any }): void;
|
||||||
children: (deleteProperty: OpenJsonModalFunc) => React.ReactNode;
|
children: (openModal: OpenJsonModalFunc) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
@ -126,10 +127,13 @@ const getErrorMessage = (error: MappingsValidationError) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const areAllObjectKeysValidParameters = (obj: { [key: string]: any }) =>
|
||||||
|
Object.keys(obj).every(key => VALID_MAPPINGS_PARAMETERS.includes(key));
|
||||||
|
|
||||||
export const LoadMappingsProvider = ({ onJson, children }: Props) => {
|
export const LoadMappingsProvider = ({ onJson, children }: Props) => {
|
||||||
const [state, setState] = useState<State>({ isModalOpen: false });
|
const [state, setState] = useState<State>({ isModalOpen: false });
|
||||||
const [totalErrorsToDisplay, setTotalErrorsToDisplay] = useState<number>(MAX_ERRORS_TO_DISPLAY);
|
const [totalErrorsToDisplay, setTotalErrorsToDisplay] = useState<number>(MAX_ERRORS_TO_DISPLAY);
|
||||||
const jsonContent = useRef<Parameters<OnJsonEditorUpdateHandler>['0'] | undefined>();
|
const jsonContent = useRef<Parameters<OnJsonEditorUpdateHandler>['0'] | undefined>(undefined);
|
||||||
const view: ModalView =
|
const view: ModalView =
|
||||||
state.json !== undefined && state.errors !== undefined ? 'validationResult' : 'json';
|
state.json !== undefined && state.errors !== undefined ? 'validationResult' : 'json';
|
||||||
const i18nTexts = getTexts(view, state.errors?.length);
|
const i18nTexts = getTexts(view, state.errors?.length);
|
||||||
|
@ -146,6 +150,44 @@ export const LoadMappingsProvider = ({ onJson, children }: Props) => {
|
||||||
setState({ isModalOpen: false });
|
setState({ isModalOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMappingsMetadata = (unparsed: {
|
||||||
|
[key: string]: any;
|
||||||
|
}): { customType?: string; isMultiTypeMappings: boolean } => {
|
||||||
|
let hasCustomType = false;
|
||||||
|
let isMultiTypeMappings = false;
|
||||||
|
let customType: string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to check if there are single or multi-types mappings declared, for that we will check for the following:
|
||||||
|
*
|
||||||
|
* - Are **all** root level keys valid parameter for the mappings definition. If not, and all keys are plain object, we assume we have multi-type mappings
|
||||||
|
* - If there are more than two types, return "as is" as the UI does not support more than 1 type and will display a warning callout
|
||||||
|
* - If there is only 1 type, validate the mappings definition and return it wrapped inside the the custom type
|
||||||
|
*/
|
||||||
|
const areAllKeysValid = areAllObjectKeysValidParameters(unparsed);
|
||||||
|
const areAllValuesPlainObjects = Object.values(unparsed).every(isPlainObject);
|
||||||
|
const areAllValuesObjKeysValidParameterName =
|
||||||
|
areAllValuesPlainObjects && Object.values(unparsed).every(areAllObjectKeysValidParameters);
|
||||||
|
|
||||||
|
if (!areAllKeysValid && areAllValuesPlainObjects) {
|
||||||
|
hasCustomType = true;
|
||||||
|
isMultiTypeMappings = Object.keys(unparsed).length > 1;
|
||||||
|
}
|
||||||
|
// If all root level keys are *valid* parameters BUT they are all plain objects which *also* have ALL valid mappings config parameter
|
||||||
|
// we can assume that they are custom types whose name matches a mappings configuration parameter.
|
||||||
|
// This is to handle the case where a custom type would be for example "dynamic" which is a mappings configuration parameter.
|
||||||
|
else if (areAllKeysValid && areAllValuesPlainObjects && areAllValuesObjKeysValidParameterName) {
|
||||||
|
hasCustomType = true;
|
||||||
|
isMultiTypeMappings = Object.keys(unparsed).length > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCustomType && !isMultiTypeMappings) {
|
||||||
|
customType = Object.keys(unparsed)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isMultiTypeMappings, customType };
|
||||||
|
};
|
||||||
|
|
||||||
const loadJson = () => {
|
const loadJson = () => {
|
||||||
if (jsonContent.current === undefined) {
|
if (jsonContent.current === undefined) {
|
||||||
// No changes have been made in the JSON, this is probably a "reset()" for the user
|
// No changes have been made in the JSON, this is probably a "reset()" for the user
|
||||||
|
@ -159,14 +201,41 @@ export const LoadMappingsProvider = ({ onJson, children }: Props) => {
|
||||||
if (isValidJson) {
|
if (isValidJson) {
|
||||||
// Parse and validate the JSON to make sure it won't break the UI
|
// Parse and validate the JSON to make sure it won't break the UI
|
||||||
const unparsed = jsonContent.current.data.format();
|
const unparsed = jsonContent.current.data.format();
|
||||||
const { value: parsed, errors } = validateMappings(unparsed);
|
|
||||||
|
|
||||||
if (errors) {
|
if (Object.keys(unparsed).length === 0) {
|
||||||
setState({ isModalOpen: true, json: { unparsed, parsed }, errors });
|
// Empty object...exit early
|
||||||
|
onJson(unparsed);
|
||||||
|
closeModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onJson(parsed);
|
let mappingsToValidate = unparsed;
|
||||||
|
const { isMultiTypeMappings, customType } = getMappingsMetadata(unparsed);
|
||||||
|
|
||||||
|
if (isMultiTypeMappings) {
|
||||||
|
// Exit early, the UI will show a warning
|
||||||
|
onJson(unparsed);
|
||||||
|
closeModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom type can't be "properties", ES will not treat it as such
|
||||||
|
// as it is reserved for fields definition
|
||||||
|
if (customType !== undefined && customType !== 'properties') {
|
||||||
|
mappingsToValidate = unparsed[customType];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value: parsed, errors } = validateMappings(mappingsToValidate);
|
||||||
|
|
||||||
|
// Wrap the mappings definition with custom type if one was provided.
|
||||||
|
const parsedWithType = customType !== undefined ? { [customType]: parsed } : parsed;
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
setState({ isModalOpen: true, json: { unparsed, parsed: parsedWithType }, errors });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onJson(parsedWithType);
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { documentationService } from '../../../services/documentation';
|
||||||
export const MultipleMappingsWarning = () => (
|
export const MultipleMappingsWarning = () => (
|
||||||
<EuiCallOut
|
<EuiCallOut
|
||||||
title={i18n.translate('xpack.idxMgmt.mappingsEditor.mappingTypesDetectedCallOutTitle', {
|
title={i18n.translate('xpack.idxMgmt.mappingsEditor.mappingTypesDetectedCallOutTitle', {
|
||||||
defaultMessage: 'Mapping types detected',
|
defaultMessage: 'Multiple mapping types detected',
|
||||||
})}
|
})}
|
||||||
iconType="alert"
|
iconType="alert"
|
||||||
color="warning"
|
color="warning"
|
||||||
|
@ -23,7 +23,7 @@ export const MultipleMappingsWarning = () => (
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="xpack.idxMgmt.mappingsEditor.mappingTypesDetectedCallOutDescription"
|
id="xpack.idxMgmt.mappingsEditor.mappingTypesDetectedCallOutDescription"
|
||||||
defaultMessage="The mappings for this template uses types, which have been removed. {docsLink}"
|
defaultMessage="The mappings for this template uses multiple types, which are not supported. {docsLink}"
|
||||||
values={{
|
values={{
|
||||||
docsLink: (
|
docsLink: (
|
||||||
<EuiLink href={documentationService.getAlternativeToMappingTypesLink()} target="_blank">
|
<EuiLink href={documentationService.getAlternativeToMappingTypesLink()} target="_blank">
|
||||||
|
|
|
@ -11,3 +11,5 @@ export * from './mappings_editor';
|
||||||
export * from './components/load_mappings';
|
export * from './components/load_mappings';
|
||||||
|
|
||||||
export { OnUpdateHandler, Types } from './mappings_state';
|
export { OnUpdateHandler, Types } from './mappings_state';
|
||||||
|
|
||||||
|
export { doMappingsHaveType } from './lib';
|
||||||
|
|
|
@ -73,7 +73,10 @@ describe('extractMappingsDefinition', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(extractMappingsDefinition(mappings)).toBe(mappings.type2);
|
expect(extractMappingsDefinition(mappings)).toEqual({
|
||||||
|
type: 'type2',
|
||||||
|
mappings: mappings.type2,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should detect that the mappings has one type and return its mapping definition', () => {
|
test('should detect that the mappings has one type and return its mapping definition', () => {
|
||||||
|
@ -97,7 +100,37 @@ describe('extractMappingsDefinition', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(extractMappingsDefinition(mappings)).toBe(mappings.myType);
|
expect(extractMappingsDefinition(mappings)).toEqual({
|
||||||
|
type: 'myType',
|
||||||
|
mappings: mappings.myType,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should detect that the mappings has one custom type whose name matches a mappings definition parameter', () => {
|
||||||
|
const mappings = {
|
||||||
|
dynamic: {
|
||||||
|
_source: {
|
||||||
|
excludes: [],
|
||||||
|
includes: [],
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
_meta: {},
|
||||||
|
_routing: {
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
dynamic: true,
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
type: 'keyword',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(extractMappingsDefinition(mappings)).toEqual({
|
||||||
|
type: 'dynamic',
|
||||||
|
mappings: mappings.dynamic,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should detect that the mappings has one type at root level', () => {
|
test('should detect that the mappings has one type at root level', () => {
|
||||||
|
@ -123,6 +156,6 @@ describe('extractMappingsDefinition', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(extractMappingsDefinition(mappings)).toBe(mappings);
|
expect(extractMappingsDefinition(mappings)).toEqual({ mappings });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,15 +6,15 @@
|
||||||
import { isPlainObject } from 'lodash';
|
import { isPlainObject } from 'lodash';
|
||||||
|
|
||||||
import { GenericObject } from '../types';
|
import { GenericObject } from '../types';
|
||||||
import {
|
import { validateMappingsConfiguration, VALID_MAPPINGS_PARAMETERS } from './mappings_validator';
|
||||||
validateMappingsConfiguration,
|
|
||||||
mappingsConfigurationSchemaKeys,
|
|
||||||
} from './mappings_validator';
|
|
||||||
|
|
||||||
const ALLOWED_PARAMETERS = [...mappingsConfigurationSchemaKeys, 'dynamic_templates', 'properties'];
|
interface MappingsWithType {
|
||||||
|
type?: string;
|
||||||
|
mappings: GenericObject;
|
||||||
|
}
|
||||||
|
|
||||||
const isMappingDefinition = (obj: GenericObject): boolean => {
|
const isMappingDefinition = (obj: GenericObject): boolean => {
|
||||||
const areAllKeysValid = Object.keys(obj).every(key => ALLOWED_PARAMETERS.includes(key));
|
const areAllKeysValid = Object.keys(obj).every(key => VALID_MAPPINGS_PARAMETERS.includes(key));
|
||||||
|
|
||||||
if (!areAllKeysValid) {
|
if (!areAllKeysValid) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -32,6 +32,29 @@ const isMappingDefinition = (obj: GenericObject): boolean => {
|
||||||
return isConfigurationValid && isPropertiesValid && isDynamicTemplatesValid;
|
return isConfigurationValid && isPropertiesValid && isDynamicTemplatesValid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMappingsDefinitionWithType = (mappings: GenericObject): MappingsWithType[] => {
|
||||||
|
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.entries(mappings).reduce(
|
||||||
|
(acc: Array<{ type: string; mappings: GenericObject }>, [type, value]) => {
|
||||||
|
if (isMappingDefinition(value)) {
|
||||||
|
acc.push({ type, mappings: value as GenericObject });
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
return typedMappings;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const doMappingsHaveType = (mappings: GenericObject = {}): boolean =>
|
||||||
|
getMappingsDefinitionWithType(mappings).filter(({ type }) => type !== undefined).length > 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 5.x index templates can be created with multiple types.
|
* 5.x index templates can be created with multiple types.
|
||||||
* e.g.
|
* e.g.
|
||||||
|
@ -72,19 +95,10 @@ const isMappingDefinition = (obj: GenericObject): boolean => {
|
||||||
*
|
*
|
||||||
* @param mappings The mappings object to validate
|
* @param mappings The mappings object to validate
|
||||||
*/
|
*/
|
||||||
export const extractMappingsDefinition = (mappings: GenericObject = {}): GenericObject | null => {
|
export const extractMappingsDefinition = (
|
||||||
if (isMappingDefinition(mappings)) {
|
mappings: GenericObject = {}
|
||||||
// No need to go any further
|
): MappingsWithType | null => {
|
||||||
return mappings;
|
const typedMappings = getMappingsDefinitionWithType(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
|
// If there are no typed mappings found this means that one of the type must did not pass
|
||||||
// the "isMappingDefinition()" validation.
|
// the "isMappingDefinition()" validation.
|
||||||
|
|
|
@ -18,6 +18,24 @@ describe('Mappings configuration validator', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should detect valid mappings configuration', () => {
|
||||||
|
const mappings = {
|
||||||
|
_source: {
|
||||||
|
includes: [],
|
||||||
|
excludes: [],
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
_meta: {},
|
||||||
|
_routing: {
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
dynamic: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { errors } = validateMappings(mappings);
|
||||||
|
expect(errors).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
it('should strip out unknown configuration', () => {
|
it('should strip out unknown configuration', () => {
|
||||||
const mappings = {
|
const mappings = {
|
||||||
dynamic: true,
|
dynamic: true,
|
||||||
|
@ -30,6 +48,7 @@ describe('Mappings configuration validator', () => {
|
||||||
excludes: ['abc'],
|
excludes: ['abc'],
|
||||||
},
|
},
|
||||||
properties: { title: { type: 'text' } },
|
properties: { title: { type: 'text' } },
|
||||||
|
dynamic_templates: [],
|
||||||
unknown: 123,
|
unknown: 123,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,7 +56,7 @@ describe('Mappings configuration validator', () => {
|
||||||
|
|
||||||
const { unknown, ...expected } = mappings;
|
const { unknown, ...expected } = mappings;
|
||||||
expect(value).toEqual(expected);
|
expect(value).toEqual(expected);
|
||||||
expect(errors).toBe(undefined);
|
expect(errors).toEqual([{ code: 'ERR_CONFIG', configName: 'unknown' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should strip out invalid configuration and returns the errors for each of them', () => {
|
it('should strip out invalid configuration and returns the errors for each of them', () => {
|
||||||
|
@ -47,9 +66,8 @@ describe('Mappings configuration validator', () => {
|
||||||
dynamic_date_formats: false, // wrong format
|
dynamic_date_formats: false, // wrong format
|
||||||
_source: {
|
_source: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
includes: 'abc',
|
unknownProp: 'abc', // invalid
|
||||||
excludes: ['abc'],
|
excludes: ['abc'],
|
||||||
wrong: 123, // parameter not allowed
|
|
||||||
},
|
},
|
||||||
properties: 'abc',
|
properties: 'abc',
|
||||||
};
|
};
|
||||||
|
@ -59,10 +77,10 @@ describe('Mappings configuration validator', () => {
|
||||||
expect(value).toEqual({
|
expect(value).toEqual({
|
||||||
dynamic: true,
|
dynamic: true,
|
||||||
properties: {},
|
properties: {},
|
||||||
|
dynamic_templates: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(errors).not.toBe(undefined);
|
expect(errors).not.toBe(undefined);
|
||||||
expect(errors!.length).toBe(3);
|
|
||||||
expect(errors!).toEqual([
|
expect(errors!).toEqual([
|
||||||
{ code: 'ERR_CONFIG', configName: '_source' },
|
{ code: 'ERR_CONFIG', configName: '_source' },
|
||||||
{ code: 'ERR_CONFIG', configName: 'dynamic_date_formats' },
|
{ code: 'ERR_CONFIG', configName: 'dynamic_date_formats' },
|
||||||
|
|
|
@ -196,23 +196,30 @@ export const validateProperties = (properties = {}): PropertiesValidatorResponse
|
||||||
* Single source of truth to validate the *configuration* of the mappings.
|
* Single source of truth to validate the *configuration* of the mappings.
|
||||||
* Whenever a user loads a JSON object it will be validate against this Joi schema.
|
* Whenever a user loads a JSON object it will be validate against this Joi schema.
|
||||||
*/
|
*/
|
||||||
export const mappingsConfigurationSchema = t.partial({
|
export const mappingsConfigurationSchema = t.exact(
|
||||||
|
t.partial({
|
||||||
dynamic: t.union([t.literal(true), t.literal(false), t.literal('strict')]),
|
dynamic: t.union([t.literal(true), t.literal(false), t.literal('strict')]),
|
||||||
date_detection: t.boolean,
|
date_detection: t.boolean,
|
||||||
numeric_detection: t.boolean,
|
numeric_detection: t.boolean,
|
||||||
dynamic_date_formats: t.array(t.string),
|
dynamic_date_formats: t.array(t.string),
|
||||||
_source: t.partial({
|
_source: t.exact(
|
||||||
|
t.partial({
|
||||||
enabled: t.boolean,
|
enabled: t.boolean,
|
||||||
includes: t.array(t.string),
|
includes: t.array(t.string),
|
||||||
excludes: t.array(t.string),
|
excludes: t.array(t.string),
|
||||||
}),
|
})
|
||||||
|
),
|
||||||
_meta: t.UnknownRecord,
|
_meta: t.UnknownRecord,
|
||||||
_routing: t.partial({
|
_routing: t.interface({
|
||||||
required: t.boolean,
|
required: t.boolean,
|
||||||
}),
|
}),
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const mappingsConfigurationSchemaKeys = Object.keys(mappingsConfigurationSchema.props);
|
const mappingsConfigurationSchemaKeys = Object.keys(mappingsConfigurationSchema.type.props);
|
||||||
|
const sourceConfigurationSchemaKeys = Object.keys(
|
||||||
|
mappingsConfigurationSchema.type.props._source.type.props
|
||||||
|
);
|
||||||
|
|
||||||
export const validateMappingsConfiguration = (
|
export const validateMappingsConfiguration = (
|
||||||
mappingsConfiguration: any
|
mappingsConfiguration: any
|
||||||
|
@ -222,8 +229,20 @@ export const validateMappingsConfiguration = (
|
||||||
|
|
||||||
let copyOfMappingsConfig = { ...mappingsConfiguration };
|
let copyOfMappingsConfig = { ...mappingsConfiguration };
|
||||||
const result = mappingsConfigurationSchema.decode(mappingsConfiguration);
|
const result = mappingsConfigurationSchema.decode(mappingsConfiguration);
|
||||||
|
const isSchemaInvalid = isLeft(result);
|
||||||
|
|
||||||
if (isLeft(result)) {
|
const unknownConfigurationParameters = Object.keys(mappingsConfiguration).filter(
|
||||||
|
key => mappingsConfigurationSchemaKeys.includes(key) === false
|
||||||
|
);
|
||||||
|
|
||||||
|
const unknownSourceConfigurationParameters =
|
||||||
|
mappingsConfiguration._source !== undefined
|
||||||
|
? Object.keys(mappingsConfiguration._source).filter(
|
||||||
|
key => sourceConfigurationSchemaKeys.includes(key) === false
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (isSchemaInvalid) {
|
||||||
/**
|
/**
|
||||||
* To keep the logic simple we will strip out the parameters that contain errors
|
* To keep the logic simple we will strip out the parameters that contain errors
|
||||||
*/
|
*/
|
||||||
|
@ -235,6 +254,15 @@ export const validateMappingsConfiguration = (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (unknownConfigurationParameters.length > 0) {
|
||||||
|
unknownConfigurationParameters.forEach(configName => configurationRemoved.add(configName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unknownSourceConfigurationParameters.length > 0) {
|
||||||
|
configurationRemoved.add('_source');
|
||||||
|
delete copyOfMappingsConfig._source;
|
||||||
|
}
|
||||||
|
|
||||||
copyOfMappingsConfig = pick(copyOfMappingsConfig, mappingsConfigurationSchemaKeys);
|
copyOfMappingsConfig = pick(copyOfMappingsConfig, mappingsConfigurationSchemaKeys);
|
||||||
|
|
||||||
const errors: MappingsValidationError[] = toArray<string>(ordString)(configurationRemoved)
|
const errors: MappingsValidationError[] = toArray<string>(ordString)(configurationRemoved)
|
||||||
|
@ -252,7 +280,7 @@ export const validateMappings = (mappings: any = {}): MappingsValidatorResponse
|
||||||
return { value: {} };
|
return { value: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { properties, dynamic_templates, ...mappingsConfiguration } = mappings;
|
const { properties, dynamic_templates: dynamicTemplates, ...mappingsConfiguration } = mappings;
|
||||||
|
|
||||||
const { value: parsedConfiguration, errors: configurationErrors } = validateMappingsConfiguration(
|
const { value: parsedConfiguration, errors: configurationErrors } = validateMappingsConfiguration(
|
||||||
mappingsConfiguration
|
mappingsConfiguration
|
||||||
|
@ -265,8 +293,14 @@ export const validateMappings = (mappings: any = {}): MappingsValidatorResponse
|
||||||
value: {
|
value: {
|
||||||
...parsedConfiguration,
|
...parsedConfiguration,
|
||||||
properties: parsedProperties,
|
properties: parsedProperties,
|
||||||
dynamic_templates,
|
dynamic_templates: dynamicTemplates ?? [],
|
||||||
},
|
},
|
||||||
errors: errors.length ? errors : undefined,
|
errors: errors.length ? errors : undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const VALID_MAPPINGS_PARAMETERS = [
|
||||||
|
...mappingsConfigurationSchemaKeys,
|
||||||
|
'dynamic_templates',
|
||||||
|
'properties',
|
||||||
|
];
|
||||||
|
|
|
@ -31,7 +31,7 @@ type TabName = 'fields' | 'advanced' | 'templates';
|
||||||
export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSettings }: Props) => {
|
export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSettings }: Props) => {
|
||||||
const [selectedTab, selectTab] = useState<TabName>('fields');
|
const [selectedTab, selectTab] = useState<TabName>('fields');
|
||||||
|
|
||||||
const { parsedDefaultValue, multipleMappingsDeclared } = useMemo(() => {
|
const { parsedDefaultValue, multipleMappingsDeclared, mappingsType } = useMemo(() => {
|
||||||
const mappingsDefinition = extractMappingsDefinition(defaultValue);
|
const mappingsDefinition = extractMappingsDefinition(defaultValue);
|
||||||
|
|
||||||
if (mappingsDefinition === null) {
|
if (mappingsDefinition === null) {
|
||||||
|
@ -48,7 +48,7 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting
|
||||||
dynamic_date_formats,
|
dynamic_date_formats,
|
||||||
properties = {},
|
properties = {},
|
||||||
dynamic_templates,
|
dynamic_templates,
|
||||||
} = mappingsDefinition;
|
} = mappingsDefinition.mappings;
|
||||||
|
|
||||||
const parsed = {
|
const parsed = {
|
||||||
configuration: {
|
configuration: {
|
||||||
|
@ -66,7 +66,11 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return { parsedDefaultValue: parsed, multipleMappingsDeclared: false };
|
return {
|
||||||
|
parsedDefaultValue: parsed,
|
||||||
|
multipleMappingsDeclared: false,
|
||||||
|
mappingsType: mappingsDefinition.type,
|
||||||
|
};
|
||||||
}, [defaultValue]);
|
}, [defaultValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -108,7 +112,11 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting
|
||||||
<MultipleMappingsWarning />
|
<MultipleMappingsWarning />
|
||||||
) : (
|
) : (
|
||||||
<IndexSettingsProvider indexSettings={indexSettings}>
|
<IndexSettingsProvider indexSettings={indexSettings}>
|
||||||
<MappingsState onUpdate={onUpdate} defaultValue={parsedDefaultValue!}>
|
<MappingsState
|
||||||
|
onUpdate={onUpdate}
|
||||||
|
defaultValue={parsedDefaultValue!}
|
||||||
|
mappingsType={mappingsType}
|
||||||
|
>
|
||||||
{({ state }) => {
|
{({ state }) => {
|
||||||
const tabToContentMap = {
|
const tabToContentMap = {
|
||||||
fields: <DocumentFields />,
|
fields: <DocumentFields />,
|
||||||
|
|
|
@ -32,7 +32,7 @@ export interface Types {
|
||||||
|
|
||||||
export interface OnUpdateHandlerArg {
|
export interface OnUpdateHandlerArg {
|
||||||
isValid?: boolean;
|
isValid?: boolean;
|
||||||
getData: (isValid: boolean) => Mappings;
|
getData: (isValid: boolean) => Mappings | { [key: string]: Mappings };
|
||||||
validate: () => Promise<boolean>;
|
validate: () => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,9 +49,11 @@ export interface Props {
|
||||||
fields: { [key: string]: Field };
|
fields: { [key: string]: Field };
|
||||||
};
|
};
|
||||||
onUpdate: OnUpdateHandler;
|
onUpdate: OnUpdateHandler;
|
||||||
|
mappingsType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MappingsState = React.memo(({ children, onUpdate, defaultValue }: Props) => {
|
export const MappingsState = React.memo(
|
||||||
|
({ children, onUpdate, defaultValue, mappingsType }: Props) => {
|
||||||
const didMountRef = useRef(false);
|
const didMountRef = useRef(false);
|
||||||
|
|
||||||
const parsedFieldsDefaultValue = useMemo(() => normalize(defaultValue.fields), [
|
const parsedFieldsDefaultValue = useMemo(() => normalize(defaultValue.fields), [
|
||||||
|
@ -133,11 +135,17 @@ export const MappingsState = React.memo(({ children, onUpdate, defaultValue }: P
|
||||||
const configurationData = nextState.configuration.data.format();
|
const configurationData = nextState.configuration.data.format();
|
||||||
const templatesData = nextState.templates.data.format();
|
const templatesData = nextState.templates.data.format();
|
||||||
|
|
||||||
return {
|
const mappings = {
|
||||||
...configurationData,
|
...configurationData,
|
||||||
...templatesData,
|
...templatesData,
|
||||||
properties: fields,
|
properties: fields,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return mappingsType === undefined
|
||||||
|
? mappings
|
||||||
|
: {
|
||||||
|
[mappingsType]: mappings,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
validate: async () => {
|
validate: async () => {
|
||||||
const configurationFormValidator =
|
const configurationFormValidator =
|
||||||
|
@ -191,7 +199,8 @@ export const MappingsState = React.memo(({ children, onUpdate, defaultValue }: P
|
||||||
<DispatchContext.Provider value={dispatch}>{children({ state })}</DispatchContext.Provider>
|
<DispatchContext.Provider value={dispatch}>{children({ state })}</DispatchContext.Provider>
|
||||||
</StateContext.Provider>
|
</StateContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const useMappingsState = () => {
|
export const useMappingsState = () => {
|
||||||
const ctx = useContext(StateContext);
|
const ctx = useContext(StateContext);
|
||||||
|
|
|
@ -38,6 +38,7 @@ import { uiMetricService } from './ui_metric';
|
||||||
import { useRequest, sendRequest } from './use_request';
|
import { useRequest, sendRequest } from './use_request';
|
||||||
import { httpService } from './http';
|
import { httpService } from './http';
|
||||||
import { Template } from '../../../common/types';
|
import { Template } from '../../../common/types';
|
||||||
|
import { doMappingsHaveType } from '../components/mappings_editor';
|
||||||
|
|
||||||
let httpClient: ng.IHttpService;
|
let httpClient: ng.IHttpService;
|
||||||
|
|
||||||
|
@ -229,10 +230,14 @@ export function loadIndexTemplate(name: Template['name']) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveTemplate(template: Template, isClone?: boolean) {
|
export async function saveTemplate(template: Template, isClone?: boolean) {
|
||||||
|
const includeTypeName = doMappingsHaveType(template.mappings);
|
||||||
const result = await sendRequest({
|
const result = await sendRequest({
|
||||||
path: `${API_BASE_PATH}/templates`,
|
path: `${API_BASE_PATH}/templates`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
body: JSON.stringify(template),
|
body: JSON.stringify(template),
|
||||||
|
query: {
|
||||||
|
include_type_name: includeTypeName,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const uimActionType = isClone ? UIM_TEMPLATE_CLONE : UIM_TEMPLATE_CREATE;
|
const uimActionType = isClone ? UIM_TEMPLATE_CLONE : UIM_TEMPLATE_CREATE;
|
||||||
|
@ -243,11 +248,15 @@ export async function saveTemplate(template: Template, isClone?: boolean) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateTemplate(template: Template) {
|
export async function updateTemplate(template: Template) {
|
||||||
|
const includeTypeName = doMappingsHaveType(template.mappings);
|
||||||
const { name } = template;
|
const { name } = template;
|
||||||
const result = await sendRequest({
|
const result = await sendRequest({
|
||||||
path: `${API_BASE_PATH}/templates/${encodeURIComponent(name)}`,
|
path: `${API_BASE_PATH}/templates/${encodeURIComponent(name)}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
body: JSON.stringify(template),
|
body: JSON.stringify(template),
|
||||||
|
query: {
|
||||||
|
include_type_name: includeTypeName,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
uiMetricService.trackMetric('count', UIM_TEMPLATE_UPDATE);
|
uiMetricService.trackMetric('count', UIM_TEMPLATE_UPDATE);
|
||||||
|
|
|
@ -17,6 +17,7 @@ const handler: RouterRouteHandler = async (request, callWithRequest) => {
|
||||||
const params = {
|
const params = {
|
||||||
expand_wildcards: 'none',
|
expand_wildcards: 'none',
|
||||||
index: indexName,
|
index: indexName,
|
||||||
|
include_type_name: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hit = await callWithRequest('indices.getMapping', params);
|
const hit = await callWithRequest('indices.getMapping', params);
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { serializeTemplate } from '../../../../common/lib';
|
||||||
|
|
||||||
const handler: RouterRouteHandler = async (req, callWithRequest) => {
|
const handler: RouterRouteHandler = async (req, callWithRequest) => {
|
||||||
const template = req.payload as Template;
|
const template = req.payload as Template;
|
||||||
|
const { include_type_name } = req.query as any;
|
||||||
const serializedTemplate = serializeTemplate(template) as TemplateEs;
|
const serializedTemplate = serializeTemplate(template) as TemplateEs;
|
||||||
|
|
||||||
const { name, order, index_patterns, version, settings, mappings, aliases } = serializedTemplate;
|
const { name, order, index_patterns, version, settings, mappings, aliases } = serializedTemplate;
|
||||||
|
@ -49,6 +50,7 @@ const handler: RouterRouteHandler = async (req, callWithRequest) => {
|
||||||
return await callWithRequest('indices.putTemplate', {
|
return await callWithRequest('indices.putTemplate', {
|
||||||
name,
|
name,
|
||||||
order,
|
order,
|
||||||
|
include_type_name,
|
||||||
body: {
|
body: {
|
||||||
index_patterns,
|
index_patterns,
|
||||||
version,
|
version,
|
||||||
|
|
|
@ -13,7 +13,9 @@ let callWithInternalUser: any;
|
||||||
const allHandler: RouterRouteHandler = async (_req, callWithRequest) => {
|
const allHandler: RouterRouteHandler = async (_req, callWithRequest) => {
|
||||||
const managedTemplatePrefix = await getManagedTemplatePrefix(callWithInternalUser);
|
const managedTemplatePrefix = await getManagedTemplatePrefix(callWithInternalUser);
|
||||||
|
|
||||||
const indexTemplatesByName = await callWithRequest('indices.getTemplate');
|
const indexTemplatesByName = await callWithRequest('indices.getTemplate', {
|
||||||
|
include_type_name: true,
|
||||||
|
});
|
||||||
|
|
||||||
return deserializeTemplateList(indexTemplatesByName, managedTemplatePrefix);
|
return deserializeTemplateList(indexTemplatesByName, managedTemplatePrefix);
|
||||||
};
|
};
|
||||||
|
@ -21,7 +23,10 @@ const allHandler: RouterRouteHandler = async (_req, callWithRequest) => {
|
||||||
const oneHandler: RouterRouteHandler = async (req, callWithRequest) => {
|
const oneHandler: RouterRouteHandler = async (req, callWithRequest) => {
|
||||||
const { name } = req.params;
|
const { name } = req.params;
|
||||||
const managedTemplatePrefix = await getManagedTemplatePrefix(callWithInternalUser);
|
const managedTemplatePrefix = await getManagedTemplatePrefix(callWithInternalUser);
|
||||||
const indexTemplateByName = await callWithRequest('indices.getTemplate', { name });
|
const indexTemplateByName = await callWithRequest('indices.getTemplate', {
|
||||||
|
name,
|
||||||
|
include_type_name: true,
|
||||||
|
});
|
||||||
|
|
||||||
if (indexTemplateByName[name]) {
|
if (indexTemplateByName[name]) {
|
||||||
return deserializeTemplate({ ...indexTemplateByName[name], name }, managedTemplatePrefix);
|
return deserializeTemplate({ ...indexTemplateByName[name], name }, managedTemplatePrefix);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { serializeTemplate } from '../../../../common/lib';
|
||||||
|
|
||||||
const handler: RouterRouteHandler = async (req, callWithRequest) => {
|
const handler: RouterRouteHandler = async (req, callWithRequest) => {
|
||||||
const { name } = req.params;
|
const { name } = req.params;
|
||||||
|
const { include_type_name } = req.query as any;
|
||||||
const template = req.payload as Template;
|
const template = req.payload as Template;
|
||||||
const serializedTemplate = serializeTemplate(template) as TemplateEs;
|
const serializedTemplate = serializeTemplate(template) as TemplateEs;
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ const handler: RouterRouteHandler = async (req, callWithRequest) => {
|
||||||
return await callWithRequest('indices.putTemplate', {
|
return await callWithRequest('indices.putTemplate', {
|
||||||
name,
|
name,
|
||||||
order,
|
order,
|
||||||
|
include_type_name,
|
||||||
body: {
|
body: {
|
||||||
index_patterns,
|
index_patterns,
|
||||||
version,
|
version,
|
||||||
|
|
|
@ -32,7 +32,8 @@ export default function({ getService }) {
|
||||||
|
|
||||||
const { body } = await getIndexMapping(index).expect(200);
|
const { body } = await getIndexMapping(index).expect(200);
|
||||||
|
|
||||||
expect(body.mapping).to.eql(mappings);
|
// As, on 7.x we require the mappings with type (include_type_name), the default "_doc" type is returned
|
||||||
|
expect(body.mapping).to.eql({ _doc: mappings });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue