mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
This commit is contained in:
parent
68be1987a7
commit
fb7bdf8dfc
11 changed files with 82 additions and 24 deletions
|
@ -22,7 +22,7 @@ import './register_globals';
|
|||
|
||||
export { monaco } from './monaco_imports';
|
||||
export { XJsonLang } from './xjson';
|
||||
export { PainlessLang, PainlessContext } from './painless';
|
||||
export { PainlessLang, PainlessContext, PainlessAutocompleteField } from './painless';
|
||||
|
||||
/* eslint-disable-next-line @kbn/eslint/module_migration */
|
||||
import * as BarePluginApi from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
|
|
@ -23,4 +23,4 @@ import { getSuggestionProvider } from './language';
|
|||
|
||||
export const PainlessLang = { ID, getSuggestionProvider, lexerRules };
|
||||
|
||||
export { PainlessContext } from './types';
|
||||
export { PainlessContext, PainlessAutocompleteField } from './types';
|
||||
|
|
|
@ -21,7 +21,7 @@ import { monaco } from '../monaco_imports';
|
|||
|
||||
import { WorkerProxyService, EditorStateService } from './services';
|
||||
import { ID } from './constants';
|
||||
import { PainlessContext, Field } from './types';
|
||||
import { PainlessContext, PainlessAutocompleteField } from './types';
|
||||
import { PainlessWorker } from './worker';
|
||||
import { PainlessCompletionAdapter } from './completion_adapter';
|
||||
|
||||
|
@ -38,7 +38,10 @@ monaco.languages.onLanguage(ID, async () => {
|
|||
workerProxyService.setup();
|
||||
});
|
||||
|
||||
export const getSuggestionProvider = (context: PainlessContext, fields?: Field[]) => {
|
||||
export const getSuggestionProvider = (
|
||||
context: PainlessContext,
|
||||
fields?: PainlessAutocompleteField[]
|
||||
) => {
|
||||
editorStateService.setup(context, fields);
|
||||
|
||||
return new PainlessCompletionAdapter(worker, editorStateService);
|
||||
|
|
|
@ -17,16 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { PainlessContext, Field } from '../types';
|
||||
import { PainlessContext, PainlessAutocompleteField } from '../types';
|
||||
|
||||
export interface EditorState {
|
||||
context: PainlessContext;
|
||||
fields?: Field[];
|
||||
fields?: PainlessAutocompleteField[];
|
||||
}
|
||||
|
||||
export class EditorStateService {
|
||||
context: PainlessContext = 'painless_test';
|
||||
fields: Field[] = [];
|
||||
fields: PainlessAutocompleteField[] = [];
|
||||
|
||||
public getState(): EditorState {
|
||||
return {
|
||||
|
@ -35,7 +35,7 @@ export class EditorStateService {
|
|||
};
|
||||
}
|
||||
|
||||
public setup(context: PainlessContext, fields?: Field[]) {
|
||||
public setup(context: PainlessContext, fields?: PainlessAutocompleteField[]) {
|
||||
this.context = context;
|
||||
|
||||
if (fields) {
|
||||
|
|
|
@ -51,7 +51,7 @@ export interface PainlessCompletionResult {
|
|||
suggestions: PainlessCompletionItem[];
|
||||
}
|
||||
|
||||
export interface Field {
|
||||
export interface PainlessAutocompleteField {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
PainlessCompletionResult,
|
||||
PainlessCompletionItem,
|
||||
PainlessContext,
|
||||
Field,
|
||||
PainlessAutocompleteField,
|
||||
} from '../../types';
|
||||
|
||||
import {
|
||||
|
@ -124,7 +124,9 @@ export const getClassMemberSuggestions = (
|
|||
};
|
||||
};
|
||||
|
||||
export const getFieldSuggestions = (fields: Field[]): PainlessCompletionResult => {
|
||||
export const getFieldSuggestions = (
|
||||
fields: PainlessAutocompleteField[]
|
||||
): PainlessCompletionResult => {
|
||||
const suggestions: PainlessCompletionItem[] = fields.map(({ name }) => {
|
||||
return {
|
||||
label: name,
|
||||
|
@ -168,7 +170,7 @@ export const getConstructorSuggestions = (suggestions: Suggestion[]): PainlessCo
|
|||
export const getAutocompleteSuggestions = (
|
||||
painlessContext: PainlessContext,
|
||||
words: string[],
|
||||
fields?: Field[]
|
||||
fields?: PainlessAutocompleteField[]
|
||||
): PainlessCompletionResult => {
|
||||
const suggestions = mapContextToData[painlessContext].suggestions;
|
||||
// What the user is currently typing
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { PainlessCompletionResult, PainlessContext, Field } from '../types';
|
||||
import { PainlessCompletionResult, PainlessContext, PainlessAutocompleteField } from '../types';
|
||||
|
||||
import { getAutocompleteSuggestions } from './lib';
|
||||
|
||||
|
@ -25,7 +25,7 @@ export class PainlessWorker {
|
|||
public provideAutocompleteSuggestions(
|
||||
currentLineChars: string,
|
||||
context: PainlessContext,
|
||||
fields?: Field[]
|
||||
fields?: PainlessAutocompleteField[]
|
||||
): PainlessCompletionResult {
|
||||
// Array of the active line words, e.g., [boolean, isTrue, =, true]
|
||||
const words = currentLineChars.replace('\t', '').split(' ');
|
||||
|
|
|
@ -78,7 +78,10 @@ export const RuntimeFieldsList = () => {
|
|||
docLinks: docLinks!,
|
||||
ctx: {
|
||||
namesNotAllowed: Object.values(runtimeFields).map((field) => field.source.name),
|
||||
existingConcreteFields: Object.values(fields.byId).map((field) => field.source.name),
|
||||
existingConcreteFields: Object.values(fields.byId).map((field) => ({
|
||||
name: field.source.name,
|
||||
type: field.source.type,
|
||||
})),
|
||||
},
|
||||
},
|
||||
flyoutProps: {
|
||||
|
|
|
@ -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;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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', {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue