[Mappings editor] Add support for point field type (#77543)

This commit is contained in:
Alison Goryachev 2020-09-21 20:45:30 -04:00 committed by GitHub
parent 8d7aae0c21
commit 05df9efc91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 335 additions and 10 deletions

View file

@ -0,0 +1,158 @@
/*
* 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 { act } from 'react-dom/test-utils';
import { componentHelpers, MappingsEditorTestBed } from '../helpers';
const { setup, getMappingsEditorDataFactory } = componentHelpers.mappingsEditor;
// Parameters automatically added to the point datatype when saved (with the default values)
export const defaultPointParameters = {
type: 'point',
ignore_malformed: false,
ignore_z_value: true,
};
describe('Mappings editor: point datatype', () => {
/**
* Variable to store the mappings data forwarded to the consumer component
*/
let data: any;
let onChangeHandler: jest.Mock = jest.fn();
let getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler);
let testBed: MappingsEditorTestBed;
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});
beforeEach(() => {
onChangeHandler = jest.fn();
getMappingsEditorData = getMappingsEditorDataFactory(onChangeHandler);
});
test('initial view and default parameters values', async () => {
const defaultMappings = {
properties: {
myField: {
type: 'point',
},
},
};
const updatedMappings = { ...defaultMappings };
await act(async () => {
testBed = setup({ value: defaultMappings, onChange: onChangeHandler });
});
testBed.component.update();
const {
component,
actions: { startEditField, updateFieldAndCloseFlyout },
} = testBed;
// Open the flyout to edit the field
await startEditField('myField');
// Save the field and close the flyout
await updateFieldAndCloseFlyout();
// It should have the default parameters values added
updatedMappings.properties.myField = defaultPointParameters;
({ data } = await getMappingsEditorData(component));
expect(data).toEqual(updatedMappings);
});
describe('meta parameter', () => {
const defaultMappings = {
properties: {
myField: {
type: 'point',
},
},
};
const updatedMappings = { ...defaultMappings };
const metaParameter = {
meta: {
my_metadata: 'foobar',
},
};
beforeEach(async () => {
await act(async () => {
testBed = setup({ value: defaultMappings, onChange: onChangeHandler });
});
testBed.component.update();
});
test('valid meta object', async () => {
const {
component,
actions: {
startEditField,
updateFieldAndCloseFlyout,
showAdvancedSettings,
toggleFormRow,
updateJsonEditor,
},
} = testBed;
// Open the flyout to edit the field
await startEditField('myField');
await showAdvancedSettings();
// Enable the meta parameter and add value
toggleFormRow('metaParameter');
await act(async () => {
updateJsonEditor('metaParameterEditor', metaParameter.meta);
});
component.update();
// Save the field and close the flyout
await updateFieldAndCloseFlyout();
// It should have the default parameters values added, plus metadata
updatedMappings.properties.myField = {
...defaultPointParameters,
...metaParameter,
};
({ data } = await getMappingsEditorData(component));
expect(data).toEqual(updatedMappings);
});
test('strip empty string', async () => {
const {
component,
actions: { startEditField, updateFieldAndCloseFlyout, showAdvancedSettings, toggleFormRow },
} = testBed;
// Open the flyout to edit the field
await startEditField('myField');
await showAdvancedSettings();
// Enable the meta parameter
toggleFormRow('metaParameter');
// Save the field and close the flyout without adding any values to meta parameter
await updateFieldAndCloseFlyout();
// It should have the default parameters values added
updatedMappings.properties.myField = defaultPointParameters;
({ data } = await getMappingsEditorData(component));
expect(data).toEqual(updatedMappings);
});
});
});

View file

@ -239,6 +239,10 @@ const createActions = (testBed: TestBed<TestSubjects>) => {
const getCheckboxValue = (testSubject: TestSubjects): boolean =>
find(testSubject).props().checked;
const toggleFormRow = (formRowName: string) => {
form.toggleEuiSwitch(`${formRowName}.formRowToggle`);
};
return {
selectTab,
getFieldAt,
@ -252,6 +256,7 @@ const createActions = (testBed: TestBed<TestSubjects>) => {
getComboBoxValue,
getToggleValue,
getCheckboxValue,
toggleFormRow,
};
};
@ -365,4 +370,6 @@ export type TestSubjects =
| 'searchQuoteAnalyzer-custom'
| 'searchQuoteAnalyzer-toggleCustomButton'
| 'searchQuoteAnalyzer-custom.input'
| 'useSameAnalyzerForSearchCheckBox.input';
| 'useSameAnalyzerForSearchCheckBox.input'
| 'metaParameterEditor'
| string;

View file

@ -10,15 +10,18 @@ import { i18n } from '@kbn/i18n';
import { EditFieldFormRow } from '../fields/edit_field';
export const IgnoreZValueParameter = () => (
export const IgnoreZValueParameter = ({ description }: { description?: string }) => (
<EditFieldFormRow
title={i18n.translate('xpack.idxMgmt.mappingsEditor.ignoreZValueFieldTitle', {
defaultMessage: 'Ignore Z value',
})}
description={i18n.translate('xpack.idxMgmt.mappingsEditor.ignoredZValueFieldDescription', {
defaultMessage:
'Three dimension points will be accepted, but only latitude and longitude values will be indexed; the third dimension is ignored.',
})}
description={
description ||
i18n.translate('xpack.idxMgmt.mappingsEditor.ignoredZValueFieldDescription', {
defaultMessage:
'Three dimension points will be accepted, but only latitude and longitude values will be indexed; the third dimension is ignored.',
})
}
formFieldPath="ignore_z_value"
/>
);

View file

@ -32,6 +32,7 @@ export const MetaParameter: FunctionComponent<Props> = ({ defaultToggleValue })
}),
href: documentationService.getMetaLink(),
}}
data-test-subj="metaParameter"
>
<UseField
path="meta"
@ -39,6 +40,7 @@ export const MetaParameter: FunctionComponent<Props> = ({ defaultToggleValue })
component={JsonEditorField}
componentProps={{
euiCodeEditorProps: {
['data-test-subj']: 'metaParameterEditor',
height: '300px',
'aria-label': i18n.translate('xpack.idxMgmt.mappingsEditor.metaParameterAriaLabel', {
defaultMessage: 'metadata field data editor',

View file

@ -32,6 +32,7 @@ import { HistogramType } from './histogram_type';
import { ConstantKeywordType } from './constant_keyword_type';
import { RankFeatureType } from './rank_feature_type';
import { WildcardType } from './wildcard_type';
import { PointType } from './point_type';
const typeToParametersFormMap: { [key in DataType]?: ComponentType<any> } = {
alias: AliasType,
@ -60,6 +61,7 @@ const typeToParametersFormMap: { [key in DataType]?: ComponentType<any> } = {
constant_keyword: ConstantKeywordType,
rank_feature: RankFeatureType,
wildcard: WildcardType,
point: PointType,
};
export const getParametersFormForType = (

View file

@ -0,0 +1,76 @@
/*
* 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, { FunctionComponent } from 'react';
import { i18n } from '@kbn/i18n';
import { NormalizedField, Field as FieldType, ParameterName } from '../../../../types';
import { UseField, TextAreaField } from '../../../../shared_imports';
import { getFieldConfig } from '../../../../lib';
import {
IgnoreMalformedParameter,
IgnoreZValueParameter,
NullValueParameter,
MetaParameter,
} from '../../field_parameters';
import { AdvancedParametersSection, BasicParametersSection } from '../edit_field';
interface Props {
field: NormalizedField;
}
const getDefaultToggleValue = (param: ParameterName, field: FieldType) => {
return field[param] !== undefined && field[param] !== getFieldConfig(param).defaultValue;
};
export const PointType: FunctionComponent<Props> = ({ field }) => {
return (
<>
<BasicParametersSection>
<IgnoreMalformedParameter
description={i18n.translate(
'xpack.idxMgmt.mappingsEditor.point.ignoreMalformedFieldDescription',
{
defaultMessage:
'By default, documents that contain malformed points are not indexed. If enabled, these documents are indexed, but fields with malformed points are filtered out. Be careful: if too many documents are indexed this way, queries on the field become meaningless.',
}
)}
/>
</BasicParametersSection>
<AdvancedParametersSection>
<IgnoreZValueParameter
description={i18n.translate(
'xpack.idxMgmt.mappingsEditor.point.ignoreZValueFieldDescription',
{
defaultMessage:
'Three dimension points will be accepted, but only x and y values will be indexed; the third dimension is ignored.',
}
)}
/>
<NullValueParameter
defaultToggleValue={getDefaultToggleValue('null_value', field.source)}
description={i18n.translate(
'xpack.idxMgmt.mappingsEditor.point.nullValueFieldDescription',
{
defaultMessage:
'Replace explicit null values with a point value so that it can be indexed and searched.',
}
)}
>
<UseField
path="null_value"
component={TextAreaField}
config={getFieldConfig('null_value_point')}
/>
</NullValueParameter>
<MetaParameter defaultToggleValue={getDefaultToggleValue('meta', field.source)} />
</AdvancedParametersSection>
</>
);
};

View file

@ -821,6 +821,26 @@ export const TYPE_DEFINITION: { [key in DataType]: DataTypeDefinition } = {
</p>
),
},
point: {
label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.pointDescription', {
defaultMessage: 'Point',
}),
value: 'point',
documentation: {
main: '/point.html',
},
description: () => (
<p>
<FormattedMessage
id="xpack.idxMgmt.mappingsEditor.dataType.pointLongDescription"
defaultMessage="Point fields enable searching of {code} pairs that fall in a 2-dimensional planar coordinate system."
values={{
code: <EuiCode inline>{'x,y'}</EuiCode>,
}}
/>
</p>
),
},
wildcard: {
label: i18n.translate('xpack.idxMgmt.mappingsEditor.dataType.wildcardDescription', {
defaultMessage: 'Wildcard',
@ -882,6 +902,7 @@ export const MAIN_TYPES: MainType[] = [
'token_count',
'histogram',
'wildcard',
'point',
'other',
];

View file

@ -382,6 +382,50 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio
},
schema: t.any,
},
null_value_point: {
fieldConfig: {
defaultValue: '',
label: nullValueLabel,
helpText: () => (
<FormattedMessage
id="xpack.idxMgmt.mappingsEditor.parameters.pointNullValueHelpText"
defaultMessage="Points can be expressed as an object, string, array or {docsLink} POINT."
values={{
docsLink: (
<EuiLink href={documentationService.getWellKnownTextLink()} target="_blank">
{i18n.translate(
'xpack.idxMgmt.mappingsEditor.parameters.pointWellKnownTextDocumentationLink',
{
defaultMessage: 'Well-Known Text',
}
)}
</EuiLink>
),
}}
/>
),
validations: [
{
validator: nullValueValidateEmptyField,
},
],
deserializer: (value: any) => {
if (value === '') {
return value;
}
return JSON.stringify(value);
},
serializer: (value: string) => {
try {
return JSON.parse(value);
} catch (error) {
// swallow error and return non-parsed value;
return value;
}
},
},
schema: t.any,
},
copy_to: {
fieldConfig: {
defaultValue: '',
@ -476,12 +520,22 @@ export const PARAMETERS_DEFINITION: { [key in ParameterName]: ParameterDefinitio
return JSON.stringify(value, null, 2);
},
serializer: (value: string) => {
const parsed = JSON.parse(value);
// If an empty object was passed, strip out this value entirely.
if (!Object.keys(parsed).length) {
// Strip out empty strings
if (value.trim() === '') {
return undefined;
}
return parsed;
try {
const parsed = JSON.parse(value);
// If an empty object was passed, strip out this value entirely.
if (!Object.keys(parsed).length) {
return undefined;
}
return parsed;
} catch (error) {
// swallow error and return non-parsed value;
return value;
}
},
},
schema: t.any,

View file

@ -59,6 +59,7 @@ export type MainType =
| 'geo_point'
| 'geo_shape'
| 'token_count'
| 'point'
| 'histogram'
| 'constant_keyword'
| 'wildcard'
@ -109,6 +110,7 @@ export type ParameterName =
| 'null_value_boolean'
| 'null_value_geo_point'
| 'null_value_ip'
| 'null_value_point'
| 'copy_to'
| 'dynamic'
| 'dynamic_toggle'