mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Mappings editor] Add support for point field type (#77543)
This commit is contained in:
parent
8d7aae0c21
commit
05df9efc91
9 changed files with 335 additions and 10 deletions
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 = (
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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',
|
||||
];
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue