Integrate painless autocomplete in runtime fields editor (#84943)

This commit is contained in:
Alison Goryachev 2020-12-07 12:55:53 -05:00 committed by GitHub
parent d19b558c88
commit 079a7e82ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 82 additions and 24 deletions

View file

@ -90,8 +90,12 @@ interface Context {
* An array of existing concrete fields. If the user gives a name to the runtime
* field that matches one of the concrete fields, a callout will be displayed
* to indicate that this runtime field will shadow the concrete field.
* This array is also used to provide the list of field autocomplete suggestions to the code editor
*/
existingConcreteFields?: string[];
existingConcreteFields?: Array<{
name: string;
type: string;
}>;
}
```

View file

@ -78,7 +78,7 @@ describe('Runtime field editor', () => {
});
test('should accept a list of existing concrete fields and display a callout when shadowing one of the fields', async () => {
const existingConcreteFields = ['myConcreteField'];
const existingConcreteFields = [{ name: 'myConcreteField', type: 'keyword' }];
testBed = setup({ onChange, docLinks, ctx: { existingConcreteFields } });
@ -87,7 +87,7 @@ describe('Runtime field editor', () => {
expect(exists('shadowingFieldCallout')).toBe(false);
await act(async () => {
form.setInputValue('nameField.input', existingConcreteFields[0]);
form.setInputValue('nameField.input', existingConcreteFields[0].name);
});
component.update();

View file

@ -3,9 +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 React, { useEffect } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import { PainlessLang } from '@kbn/monaco';
import { PainlessLang, PainlessContext } from '@kbn/monaco';
import {
EuiFlexGroup,
EuiFlexItem,
@ -28,7 +28,7 @@ import {
ValidationFunc,
FieldConfig,
} from '../../shared_imports';
import { RuntimeField } from '../../types';
import { RuntimeField, RuntimeType } from '../../types';
import { RUNTIME_FIELD_OPTIONS } from '../../constants';
import { schema } from './schema';
@ -38,6 +38,11 @@ export interface FormState {
submit: FormHook<RuntimeField>['submit'];
}
interface Field {
name: string;
type: string;
}
export interface Props {
links: {
runtimePainless: string;
@ -54,8 +59,9 @@ export interface Props {
* An array of existing concrete fields. If the user gives a name to the runtime
* field that matches one of the concrete fields, a callout will be displayed
* to indicate that this runtime field will shadow the concrete field.
* It is also used to provide the list of field autocomplete suggestions to the code editor.
*/
existingConcreteFields?: string[];
existingConcreteFields?: Field[];
};
}
@ -105,18 +111,51 @@ const getNameFieldConfig = (
};
};
const mapReturnTypeToPainlessContext = (runtimeType: RuntimeType): PainlessContext => {
switch (runtimeType) {
case 'keyword':
return 'string_script_field_script_field';
case 'long':
return 'long_script_field_script_field';
case 'double':
return 'double_script_field_script_field';
case 'date':
return 'date_script_field';
case 'ip':
return 'ip_script_field_script_field';
case 'boolean':
return 'boolean_script_field_script_field';
default:
return 'string_script_field_script_field';
}
};
const RuntimeFieldFormComp = ({
defaultValue,
onChange,
links,
ctx: { namesNotAllowed, existingConcreteFields = [] } = {},
}: Props) => {
const typeFieldConfig = schema.type as FieldConfig<RuntimeType, RuntimeField>;
const [painlessContext, setPainlessContext] = useState<PainlessContext>(
mapReturnTypeToPainlessContext(typeFieldConfig!.defaultValue!)
);
const { form } = useForm<RuntimeField>({ defaultValue, schema });
const { submit, isValid: isFormValid, isSubmitted } = form;
const [{ name }] = useFormData<RuntimeField>({ form, watch: 'name' });
const nameFieldConfig = getNameFieldConfig(namesNotAllowed, defaultValue);
const onTypeChange = useCallback((newType: Array<EuiComboBoxOptionOption<RuntimeType>>) => {
setPainlessContext(mapReturnTypeToPainlessContext(newType[0]!.value!));
}, []);
const suggestionProvider = PainlessLang.getSuggestionProvider(
painlessContext,
existingConcreteFields
);
useEffect(() => {
if (onChange) {
onChange({ isValid: isFormValid, isSubmitted, submit });
@ -145,7 +184,10 @@ const RuntimeFieldFormComp = ({
{/* Return type */}
<EuiFlexItem>
<UseField<EuiComboBoxOptionOption[]> path="type">
<UseField<Array<EuiComboBoxOptionOption<RuntimeType>>>
path="type"
onChange={onTypeChange}
>
{({ label, value, setValue }) => {
if (value === undefined) {
return null;
@ -185,7 +227,7 @@ const RuntimeFieldFormComp = ({
</EuiFlexItem>
</EuiFlexGroup>
{existingConcreteFields.includes(name) && (
{existingConcreteFields.find((field) => field.name === name) && (
<>
<EuiSpacer />
<EuiCallOut
@ -237,6 +279,7 @@ const RuntimeFieldFormComp = ({
>
<CodeEditor
languageId={PainlessLang.ID}
suggestionProvider={suggestionProvider}
width="100%"
height="300px"
value={value}
@ -250,6 +293,9 @@ const RuntimeFieldFormComp = ({
wordWrap: 'on',
wrappingIndent: 'indent',
automaticLayout: true,
suggest: {
snippetsPreventQuickSuggestions: false,
},
}}
data-test-subj="scriptField"
aria-label={i18n.translate('xpack.runtimeFields.form.scriptEditorAriaLabel', {