mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Runtime fields] Add support in index template (#84184)
Co-authored-by: Adam Locke <adam.locke@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
43dd4876f2
commit
e83bbfd289
78 changed files with 1850 additions and 557 deletions
|
@ -35,7 +35,7 @@ const MyComponent = () => {
|
|||
|
||||
const saveRuntimeField = (field: RuntimeField) => {
|
||||
// Do something with the field
|
||||
console.log(field); // { name: 'myField', type: 'boolean', script: "return 'hello'" }
|
||||
// See interface returned in @returns section below
|
||||
};
|
||||
|
||||
const openRuntimeFieldsEditor = async() => {
|
||||
|
@ -45,6 +45,7 @@ const MyComponent = () => {
|
|||
closeRuntimeFieldEditor.current = openEditor({
|
||||
onSave: saveRuntimeField,
|
||||
/* defaultValue: optional field to edit */
|
||||
/* ctx: Context -- see section below */
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -61,7 +62,40 @@ const MyComponent = () => {
|
|||
}
|
||||
```
|
||||
|
||||
#### Alternative
|
||||
#### `@returns`
|
||||
|
||||
You get back a `RuntimeField` object with the following interface
|
||||
|
||||
```ts
|
||||
interface RuntimeField {
|
||||
name: string;
|
||||
type: RuntimeType; // 'long' | 'boolean' ...
|
||||
script: {
|
||||
source: string;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Context object
|
||||
|
||||
You can provide a context object to the runtime field editor. It has the following interface
|
||||
|
||||
```ts
|
||||
interface Context {
|
||||
/** An array of field name not allowed. You would probably provide an array of existing runtime fields
|
||||
* to prevent the user creating a field with the same name.
|
||||
*/
|
||||
namesNotAllowed?: string[];
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
existingConcreteFields?: string[];
|
||||
}
|
||||
```
|
||||
|
||||
#### Other type of integration
|
||||
|
||||
The runtime field editor is also exported as static React component that you can import into your components. The editor is exported in 2 flavours:
|
||||
|
||||
|
@ -96,6 +130,7 @@ const MyComponent = () => {
|
|||
onCancel={() => setIsFlyoutVisible(false)}
|
||||
docLinks={docLinksStart}
|
||||
defaultValue={/*optional runtime field to edit*/}
|
||||
ctx={/*optional context object -- see section above*/}
|
||||
/>
|
||||
</EuiFlyout>
|
||||
)}
|
||||
|
@ -138,6 +173,7 @@ const MyComponent = () => {
|
|||
onCancel={() => flyoutEditor.current?.close()}
|
||||
docLinks={docLinksStart}
|
||||
defaultValue={defaultRuntimeField}
|
||||
ctx={/*optional context object -- see section above*/}
|
||||
/>
|
||||
</KibanaReactContextProvider>
|
||||
)
|
||||
|
@ -182,6 +218,7 @@ const MyComponent = () => {
|
|||
onChange={setRuntimeFieldFormState}
|
||||
docLinks={docLinksStart}
|
||||
defaultValue={/*optional runtime field to edit*/}
|
||||
ctx={/*optional context object -- see section above*/}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
|
|
|
@ -8,4 +8,7 @@ export { RuntimeFieldForm, FormState as RuntimeFieldFormState } from './runtime_
|
|||
|
||||
export { RuntimeFieldEditor } from './runtime_field_editor';
|
||||
|
||||
export { RuntimeFieldEditorFlyoutContent } from './runtime_field_editor_flyout_content';
|
||||
export {
|
||||
RuntimeFieldEditorFlyoutContent,
|
||||
RuntimeFieldEditorFlyoutContentProps,
|
||||
} from './runtime_field_editor_flyout_content';
|
||||
|
|
|
@ -31,6 +31,14 @@ describe('Runtime field editor', () => {
|
|||
|
||||
const lastOnChangeCall = (): FormState[] => onChange.mock.calls[onChange.mock.calls.length - 1];
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
onChange = jest.fn();
|
||||
});
|
||||
|
@ -46,7 +54,7 @@ describe('Runtime field editor', () => {
|
|||
const defaultValue: RuntimeField = {
|
||||
name: 'foo',
|
||||
type: 'date',
|
||||
script: 'test=123',
|
||||
script: { source: 'test=123' },
|
||||
};
|
||||
testBed = setup({ onChange, defaultValue, docLinks });
|
||||
|
||||
|
@ -68,4 +76,75 @@ describe('Runtime field editor', () => {
|
|||
expect(lastState.isValid).toBe(true);
|
||||
expect(lastState.isSubmitted).toBe(true);
|
||||
});
|
||||
|
||||
test('should accept a list of existing concrete fields and display a callout when shadowing one of the fields', async () => {
|
||||
const existingConcreteFields = ['myConcreteField'];
|
||||
|
||||
testBed = setup({ onChange, docLinks, ctx: { existingConcreteFields } });
|
||||
|
||||
const { form, component, exists } = testBed;
|
||||
|
||||
expect(exists('shadowingFieldCallout')).toBe(false);
|
||||
|
||||
await act(async () => {
|
||||
form.setInputValue('nameField.input', existingConcreteFields[0]);
|
||||
});
|
||||
component.update();
|
||||
|
||||
expect(exists('shadowingFieldCallout')).toBe(true);
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
test('should accept an optional list of existing runtime fields and prevent creating duplicates', async () => {
|
||||
const existingRuntimeFieldNames = ['myRuntimeField'];
|
||||
|
||||
testBed = setup({ onChange, docLinks, ctx: { namesNotAllowed: existingRuntimeFieldNames } });
|
||||
|
||||
const { form, component } = testBed;
|
||||
|
||||
await act(async () => {
|
||||
form.setInputValue('nameField.input', existingRuntimeFieldNames[0]);
|
||||
form.setInputValue('scriptField', 'echo("hello")');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1000); // Make sure our debounced error message is in the DOM
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await lastOnChangeCall()[0].submit();
|
||||
});
|
||||
|
||||
component.update();
|
||||
|
||||
expect(lastOnChangeCall()[0].isValid).toBe(false);
|
||||
expect(form.getErrorsMessages()).toEqual(['There is already a field with this name.']);
|
||||
});
|
||||
|
||||
test('should not count the default value as a duplicate', async () => {
|
||||
const existingRuntimeFieldNames = ['myRuntimeField'];
|
||||
|
||||
const defaultValue: RuntimeField = {
|
||||
name: 'myRuntimeField',
|
||||
type: 'boolean',
|
||||
script: { source: 'emit("hello"' },
|
||||
};
|
||||
|
||||
testBed = setup({
|
||||
defaultValue,
|
||||
onChange,
|
||||
docLinks,
|
||||
ctx: { namesNotAllowed: existingRuntimeFieldNames },
|
||||
});
|
||||
|
||||
const { form } = testBed;
|
||||
|
||||
await act(async () => {
|
||||
await lastOnChangeCall()[0].submit();
|
||||
});
|
||||
|
||||
expect(lastOnChangeCall()[0].isValid).toBe(true);
|
||||
expect(form.getErrorsMessages()).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,10 +15,13 @@ export interface Props {
|
|||
docLinks: DocLinksStart;
|
||||
defaultValue?: RuntimeField;
|
||||
onChange?: FormProps['onChange'];
|
||||
ctx?: FormProps['ctx'];
|
||||
}
|
||||
|
||||
export const RuntimeFieldEditor = ({ defaultValue, onChange, docLinks }: Props) => {
|
||||
export const RuntimeFieldEditor = ({ defaultValue, onChange, docLinks, ctx }: Props) => {
|
||||
const links = getLinks(docLinks);
|
||||
|
||||
return <RuntimeFieldForm links={links} defaultValue={defaultValue} onChange={onChange} />;
|
||||
return (
|
||||
<RuntimeFieldForm links={links} defaultValue={defaultValue} onChange={onChange} ctx={ctx} />
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,4 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { RuntimeFieldEditorFlyoutContent } from './runtime_field_editor_flyout_content';
|
||||
export {
|
||||
RuntimeFieldEditorFlyoutContent,
|
||||
Props as RuntimeFieldEditorFlyoutContentProps,
|
||||
} from './runtime_field_editor_flyout_content';
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('Runtime field editor flyout', () => {
|
|||
const field: RuntimeField = {
|
||||
name: 'foo',
|
||||
type: 'date',
|
||||
script: 'test=123',
|
||||
script: { source: 'test=123' },
|
||||
};
|
||||
|
||||
const { find } = setup({ ...defaultProps, defaultValue: field });
|
||||
|
@ -47,14 +47,14 @@ describe('Runtime field editor flyout', () => {
|
|||
expect(find('flyoutTitle').text()).toBe(`Edit ${field.name} field`);
|
||||
expect(find('nameField.input').props().value).toBe(field.name);
|
||||
expect(find('typeField').props().value).toBe(field.type);
|
||||
expect(find('scriptField').props().value).toBe(field.script);
|
||||
expect(find('scriptField').props().value).toBe(field.script.source);
|
||||
});
|
||||
|
||||
test('should accept an onSave prop', async () => {
|
||||
const field: RuntimeField = {
|
||||
name: 'foo',
|
||||
type: 'date',
|
||||
script: 'test=123',
|
||||
script: { source: 'test=123' },
|
||||
};
|
||||
const onSave: jest.Mock<Props['onSave']> = jest.fn();
|
||||
|
||||
|
@ -93,10 +93,7 @@ describe('Runtime field editor flyout', () => {
|
|||
|
||||
expect(onSave).toHaveBeenCalledTimes(0);
|
||||
expect(find('saveFieldButton').props().disabled).toBe(true);
|
||||
expect(form.getErrorsMessages()).toEqual([
|
||||
'Give a name to the field.',
|
||||
'Script must emit() a value.',
|
||||
]);
|
||||
expect(form.getErrorsMessages()).toEqual(['Give a name to the field.']);
|
||||
expect(exists('formError')).toBe(true);
|
||||
expect(find('formError').text()).toBe('Fix errors in form before continuing.');
|
||||
});
|
||||
|
@ -120,7 +117,7 @@ describe('Runtime field editor flyout', () => {
|
|||
expect(fieldReturned).toEqual({
|
||||
name: 'someName',
|
||||
type: 'keyword', // default to keyword
|
||||
script: 'script=123',
|
||||
script: { source: 'script=123' },
|
||||
});
|
||||
|
||||
// Change the type and make sure it is forwarded
|
||||
|
@ -139,7 +136,7 @@ describe('Runtime field editor flyout', () => {
|
|||
expect(fieldReturned).toEqual({
|
||||
name: 'someName',
|
||||
type: 'other_type',
|
||||
script: 'script=123',
|
||||
script: { source: 'script=123' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,7 +21,10 @@ import { DocLinksStart } from 'src/core/public';
|
|||
|
||||
import { RuntimeField } from '../../types';
|
||||
import { FormState } from '../runtime_field_form';
|
||||
import { RuntimeFieldEditor } from '../runtime_field_editor';
|
||||
import {
|
||||
RuntimeFieldEditor,
|
||||
Props as RuntimeFieldEditorProps,
|
||||
} from '../runtime_field_editor/runtime_field_editor';
|
||||
|
||||
const geti18nTexts = (field?: RuntimeField) => {
|
||||
return {
|
||||
|
@ -64,6 +67,10 @@ export interface Props {
|
|||
* An optional runtime field to edit
|
||||
*/
|
||||
defaultValue?: RuntimeField;
|
||||
/**
|
||||
* Optional context object
|
||||
*/
|
||||
ctx?: RuntimeFieldEditorProps['ctx'];
|
||||
}
|
||||
|
||||
export const RuntimeFieldEditorFlyoutContent = ({
|
||||
|
@ -71,6 +78,7 @@ export const RuntimeFieldEditorFlyoutContent = ({
|
|||
onCancel,
|
||||
docLinks,
|
||||
defaultValue: field,
|
||||
ctx,
|
||||
}: Props) => {
|
||||
const i18nTexts = geti18nTexts(field);
|
||||
|
||||
|
@ -95,12 +103,17 @@ export const RuntimeFieldEditorFlyoutContent = ({
|
|||
<>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiTitle size="m" data-test-subj="flyoutTitle">
|
||||
<h2>{i18nTexts.flyoutTitle}</h2>
|
||||
<h2 id="runtimeFieldEditorEditTitle">{i18nTexts.flyoutTitle}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
|
||||
<EuiFlyoutBody>
|
||||
<RuntimeFieldEditor docLinks={docLinks} defaultValue={field} onChange={setFormState} />
|
||||
<RuntimeFieldEditor
|
||||
docLinks={docLinks}
|
||||
defaultValue={field}
|
||||
onChange={setFormState}
|
||||
ctx={ctx}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -18,7 +18,7 @@ const setup = (props?: Props) =>
|
|||
})(props) as TestBed;
|
||||
|
||||
const links = {
|
||||
painlessSyntax: 'https://jestTest.elastic.co/to-be-defined.html',
|
||||
runtimePainless: 'https://jestTest.elastic.co/to-be-defined.html',
|
||||
};
|
||||
|
||||
describe('Runtime field form', () => {
|
||||
|
@ -45,28 +45,28 @@ describe('Runtime field form', () => {
|
|||
const { exists, find } = testBed;
|
||||
|
||||
expect(exists('painlessSyntaxLearnMoreLink')).toBe(true);
|
||||
expect(find('painlessSyntaxLearnMoreLink').props().href).toBe(links.painlessSyntax);
|
||||
expect(find('painlessSyntaxLearnMoreLink').props().href).toBe(links.runtimePainless);
|
||||
});
|
||||
|
||||
test('should accept a "defaultValue" prop', () => {
|
||||
const defaultValue: RuntimeField = {
|
||||
name: 'foo',
|
||||
type: 'date',
|
||||
script: 'test=123',
|
||||
script: { source: 'test=123' },
|
||||
};
|
||||
testBed = setup({ defaultValue, links });
|
||||
const { find } = testBed;
|
||||
|
||||
expect(find('nameField.input').props().value).toBe(defaultValue.name);
|
||||
expect(find('typeField').props().value).toBe(defaultValue.type);
|
||||
expect(find('scriptField').props().value).toBe(defaultValue.script);
|
||||
expect(find('scriptField').props().value).toBe(defaultValue.script.source);
|
||||
});
|
||||
|
||||
test('should accept an "onChange" prop to forward the form state', async () => {
|
||||
const defaultValue: RuntimeField = {
|
||||
name: 'foo',
|
||||
type: 'date',
|
||||
script: 'test=123',
|
||||
script: { source: 'test=123' },
|
||||
};
|
||||
testBed = setup({ onChange, defaultValue, links });
|
||||
|
||||
|
|
|
@ -14,9 +14,20 @@ import {
|
|||
EuiComboBox,
|
||||
EuiComboBoxOptionOption,
|
||||
EuiLink,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { useForm, Form, FormHook, UseField, TextField, CodeEditor } from '../../shared_imports';
|
||||
import {
|
||||
useForm,
|
||||
useFormData,
|
||||
Form,
|
||||
FormHook,
|
||||
UseField,
|
||||
TextField,
|
||||
CodeEditor,
|
||||
ValidationFunc,
|
||||
FieldConfig,
|
||||
} from '../../shared_imports';
|
||||
import { RuntimeField } from '../../types';
|
||||
import { RUNTIME_FIELD_OPTIONS } from '../../constants';
|
||||
import { schema } from './schema';
|
||||
|
@ -29,15 +40,82 @@ export interface FormState {
|
|||
|
||||
export interface Props {
|
||||
links: {
|
||||
painlessSyntax: string;
|
||||
runtimePainless: string;
|
||||
};
|
||||
defaultValue?: RuntimeField;
|
||||
onChange?: (state: FormState) => void;
|
||||
/**
|
||||
* Optional context object
|
||||
*/
|
||||
ctx?: {
|
||||
/** An array of field name not allowed */
|
||||
namesNotAllowed?: string[];
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
existingConcreteFields?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
const RuntimeFieldFormComp = ({ defaultValue, onChange, links }: Props) => {
|
||||
const createNameNotAllowedValidator = (
|
||||
namesNotAllowed: string[]
|
||||
): ValidationFunc<{}, string, string> => ({ value }) => {
|
||||
if (namesNotAllowed.includes(value)) {
|
||||
return {
|
||||
message: i18n.translate(
|
||||
'xpack.runtimeFields.runtimeFieldsEditor.existRuntimeFieldNamesValidationErrorMessage',
|
||||
{
|
||||
defaultMessage: 'There is already a field with this name.',
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dynamically retrieve the config for the "name" field, adding
|
||||
* a validator to avoid duplicated runtime fields to be created.
|
||||
*
|
||||
* @param namesNotAllowed Array of names not allowed for the field "name"
|
||||
* @param defaultValue Initial value of the form
|
||||
*/
|
||||
const getNameFieldConfig = (
|
||||
namesNotAllowed?: string[],
|
||||
defaultValue?: Props['defaultValue']
|
||||
): FieldConfig<string, RuntimeField> => {
|
||||
const nameFieldConfig = schema.name as FieldConfig<string, RuntimeField>;
|
||||
|
||||
if (!namesNotAllowed) {
|
||||
return nameFieldConfig;
|
||||
}
|
||||
|
||||
// Add validation to not allow duplicates
|
||||
return {
|
||||
...nameFieldConfig!,
|
||||
validations: [
|
||||
...(nameFieldConfig.validations ?? []),
|
||||
{
|
||||
validator: createNameNotAllowedValidator(
|
||||
namesNotAllowed.filter((name) => name !== defaultValue?.name)
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const RuntimeFieldFormComp = ({
|
||||
defaultValue,
|
||||
onChange,
|
||||
links,
|
||||
ctx: { namesNotAllowed, existingConcreteFields = [] } = {},
|
||||
}: Props) => {
|
||||
const { form } = useForm<RuntimeField>({ defaultValue, schema });
|
||||
const { submit, isValid: isFormValid, isSubmitted } = form;
|
||||
const [{ name }] = useFormData<RuntimeField>({ form, watch: 'name' });
|
||||
|
||||
const nameFieldConfig = getNameFieldConfig(namesNotAllowed, defaultValue);
|
||||
|
||||
useEffect(() => {
|
||||
if (onChange) {
|
||||
|
@ -50,7 +128,19 @@ const RuntimeFieldFormComp = ({ defaultValue, onChange, links }: Props) => {
|
|||
<EuiFlexGroup>
|
||||
{/* Name */}
|
||||
<EuiFlexItem>
|
||||
<UseField path="name" component={TextField} data-test-subj="nameField" />
|
||||
<UseField<string, RuntimeField>
|
||||
path="name"
|
||||
config={nameFieldConfig}
|
||||
component={TextField}
|
||||
data-test-subj="nameField"
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'aria-label': i18n.translate('xpack.runtimeFields.form.nameAriaLabel', {
|
||||
defaultMessage: 'Name field',
|
||||
}),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* Return type */}
|
||||
|
@ -82,6 +172,9 @@ const RuntimeFieldFormComp = ({ defaultValue, onChange, links }: Props) => {
|
|||
}}
|
||||
isClearable={false}
|
||||
data-test-subj="typeField"
|
||||
aria-label={i18n.translate('xpack.runtimeFields.form.typeSelectAriaLabel', {
|
||||
defaultMessage: 'Type select',
|
||||
})}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
@ -92,10 +185,32 @@ const RuntimeFieldFormComp = ({ defaultValue, onChange, links }: Props) => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{existingConcreteFields.includes(name) && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.runtimeFields.form.fieldShadowingCalloutTitle', {
|
||||
defaultMessage: 'Field shadowing',
|
||||
})}
|
||||
color="warning"
|
||||
iconType="pin"
|
||||
size="s"
|
||||
data-test-subj="shadowingFieldCallout"
|
||||
>
|
||||
<div>
|
||||
{i18n.translate('xpack.runtimeFields.form.fieldShadowingCalloutDescription', {
|
||||
defaultMessage:
|
||||
'This field shares the name of a mapped field. Values for this field will be returned in search results.',
|
||||
})}
|
||||
</div>
|
||||
</EuiCallOut>
|
||||
</>
|
||||
)}
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
{/* Script */}
|
||||
<UseField<string> path="script">
|
||||
<UseField<string> path="script.source">
|
||||
{({ value, setValue, label, isValid, getErrorsMessages }) => {
|
||||
return (
|
||||
<EuiFormRow
|
||||
|
@ -106,7 +221,7 @@ const RuntimeFieldFormComp = ({ defaultValue, onChange, links }: Props) => {
|
|||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
href={links.painlessSyntax}
|
||||
href={links.runtimePainless}
|
||||
target="_blank"
|
||||
external
|
||||
data-test-subj="painlessSyntaxLearnMoreLink"
|
||||
|
@ -137,6 +252,9 @@ const RuntimeFieldFormComp = ({ defaultValue, onChange, links }: Props) => {
|
|||
automaticLayout: true,
|
||||
}}
|
||||
data-test-subj="scriptField"
|
||||
aria-label={i18n.translate('xpack.runtimeFields.form.scriptEditorAriaLabel', {
|
||||
defaultMessage: 'Script editor',
|
||||
})}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
|
|
|
@ -42,17 +42,10 @@ export const schema: FormSchema<RuntimeField> = {
|
|||
serializer: (value: Array<ComboBoxOption<RuntimeType>>) => value[0].value!,
|
||||
},
|
||||
script: {
|
||||
label: i18n.translate('xpack.runtimeFields.form.defineFieldLabel', {
|
||||
defaultMessage: 'Define field',
|
||||
}),
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(
|
||||
i18n.translate('xpack.runtimeFields.form.validations.scriptIsRequiredErrorMessage', {
|
||||
defaultMessage: 'Script must emit() a value.',
|
||||
})
|
||||
),
|
||||
},
|
||||
],
|
||||
source: {
|
||||
label: i18n.translate('xpack.runtimeFields.form.defineFieldLabel', {
|
||||
defaultMessage: 'Define field (optional)',
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import { RuntimeFieldsPlugin } from './plugin';
|
|||
|
||||
export {
|
||||
RuntimeFieldEditorFlyoutContent,
|
||||
RuntimeFieldEditorFlyoutContentProps,
|
||||
RuntimeFieldEditor,
|
||||
RuntimeFieldFormState,
|
||||
} from './components';
|
||||
|
|
|
@ -8,9 +8,11 @@ import { DocLinksStart } from 'src/core/public';
|
|||
export const getLinks = (docLinks: DocLinksStart) => {
|
||||
const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docLinks;
|
||||
const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`;
|
||||
const esDocsBase = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`;
|
||||
const painlessDocsBase = `${docsBase}/elasticsearch/painless/${DOC_LINK_VERSION}`;
|
||||
|
||||
return {
|
||||
runtimePainless: `${esDocsBase}/runtime.html#runtime-mapping-fields`,
|
||||
painlessSyntax: `${painlessDocsBase}/painless-lang-spec.html`,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,10 +8,12 @@ import { CoreSetup, OverlayRef } from 'src/core/public';
|
|||
|
||||
import { toMountPoint, createKibanaReactContext } from './shared_imports';
|
||||
import { LoadEditorResponse, RuntimeField } from './types';
|
||||
import { RuntimeFieldEditorFlyoutContentProps } from './components';
|
||||
|
||||
export interface OpenRuntimeFieldEditorProps {
|
||||
onSave(field: RuntimeField): void;
|
||||
defaultValue?: RuntimeField;
|
||||
defaultValue?: RuntimeFieldEditorFlyoutContentProps['defaultValue'];
|
||||
ctx?: RuntimeFieldEditorFlyoutContentProps['ctx'];
|
||||
}
|
||||
|
||||
export const getRuntimeFieldEditorLoader = (
|
||||
|
@ -24,10 +26,12 @@ export const getRuntimeFieldEditorLoader = (
|
|||
|
||||
let overlayRef: OverlayRef | null = null;
|
||||
|
||||
const openEditor = ({ onSave, defaultValue }: OpenRuntimeFieldEditorProps) => {
|
||||
const openEditor = ({ onSave, defaultValue, ctx }: OpenRuntimeFieldEditorProps) => {
|
||||
const closeEditor = () => {
|
||||
overlayRef?.close();
|
||||
overlayRef = null;
|
||||
if (overlayRef) {
|
||||
overlayRef.close();
|
||||
overlayRef = null;
|
||||
}
|
||||
};
|
||||
|
||||
const onSaveField = (field: RuntimeField) => {
|
||||
|
@ -43,6 +47,7 @@ export const getRuntimeFieldEditorLoader = (
|
|||
onCancel={() => overlayRef?.close()}
|
||||
docLinks={docLinks}
|
||||
defaultValue={defaultValue}
|
||||
ctx={ctx}
|
||||
/>
|
||||
</KibanaReactContextProvider>
|
||||
)
|
||||
|
|
|
@ -6,10 +6,13 @@
|
|||
|
||||
export {
|
||||
useForm,
|
||||
useFormData,
|
||||
Form,
|
||||
FormSchema,
|
||||
UseField,
|
||||
FormHook,
|
||||
ValidationFunc,
|
||||
FieldConfig,
|
||||
} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib';
|
||||
|
||||
export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers';
|
||||
|
|
|
@ -31,7 +31,9 @@ export type RuntimeType = typeof RUNTIME_FIELD_TYPES[number];
|
|||
export interface RuntimeField {
|
||||
name: string;
|
||||
type: RuntimeType;
|
||||
script: string;
|
||||
script: {
|
||||
source: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ComboBoxOption<T = unknown> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue