Skip to content

Commit 00faeb9

Browse files
[Runtime fields editor] Expose editor for consuming apps (#82116)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 2cf52e1 commit 00faeb9

File tree

18 files changed

+599
-28
lines changed

18 files changed

+599
-28
lines changed

docs/developer/plugin-list.asciidoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -466,8 +466,8 @@ Elastic.
466466
|Welcome to the Kibana rollup plugin! This plugin provides Kibana support for Elasticsearch's rollup feature. Please refer to the Elasticsearch documentation to understand rollup indices and how to create rollup jobs.
467467
468468
469-
|{kib-repo}blob/{branch}/x-pack/plugins/runtime_fields[runtimeFields]
470-
|WARNING: Missing README.
469+
|{kib-repo}blob/{branch}/x-pack/plugins/runtime_fields/README.md[runtimeFields]
470+
|Welcome to the home of the runtime field editor and everything related to runtime fields!
471471
472472
473473
|{kib-repo}blob/{branch}/x-pack/plugins/searchprofiler/README.md[searchprofiler]

packages/kbn-optimizer/limits.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,4 @@ pageLoadAssetSize:
9898
visualizations: 295025
9999
visualize: 57431
100100
watcher: 43598
101-
runtimeFields: 26275
101+
runtimeFields: 41752

x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/field_parameters/painless_script_parameter.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import React from 'react';
88
import { i18n } from '@kbn/i18n';
9+
import { PainlessLang } from '@kbn/monaco';
910
import { EuiFormRow, EuiDescribedFormGroup } from '@elastic/eui';
1011

1112
import { CodeEditor, UseField } from '../../../shared_imports';
@@ -18,19 +19,18 @@ interface Props {
1819

1920
export const PainlessScriptParameter = ({ stack }: Props) => {
2021
return (
21-
<UseField path="script.source" config={getFieldConfig('script')}>
22+
<UseField<string> path="script.source" config={getFieldConfig('script')}>
2223
{(scriptField) => {
2324
const error = scriptField.getErrorsMessages();
2425
const isInvalid = error ? Boolean(error.length) : false;
2526

2627
const field = (
2728
<EuiFormRow label={scriptField.label} error={error} isInvalid={isInvalid} fullWidth>
2829
<CodeEditor
29-
languageId="painless"
30-
// 99% width allows the editor to resize horizontally. 100% prevents it from resizing.
31-
width="99%"
30+
languageId={PainlessLang.ID}
31+
width="100%"
3232
height="400px"
33-
value={scriptField.value as string}
33+
value={scriptField.value}
3434
onChange={scriptField.setValue}
3535
options={{
3636
fontSize: 12,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# Runtime fields
2+
3+
Welcome to the home of the runtime field editor and everything related to runtime fields!
4+
5+
## The runtime field editor
6+
7+
The runtime field editor is exported in 2 flavours:
8+
9+
* As the content of a `<EuiFlyout />`
10+
* As a standalone component that you can inline anywhere
11+
12+
### Content of a `<EuiFlyout />`
13+
14+
```js
15+
import React, { useState } from 'react';
16+
import { EuiFlyoutBody, EuiButton } from '@elastic/eui';
17+
import { RuntimeFieldEditorFlyoutContent, RuntimeField } from '../runtime_fields/public';
18+
19+
const MyComponent = () => {
20+
const { docLinksStart } = useCoreContext(); // access the core start service
21+
const [isFlyoutVisilbe, setIsFlyoutVisible] = useState(false);
22+
23+
const saveRuntimeField = useCallback((field: RuntimeField) => {
24+
// Do something with the field
25+
}, []);
26+
27+
return (
28+
<>
29+
<EuiButton onClick={() => setIsFlyoutVisible(true)}>Create field</EuiButton>
30+
31+
{isFlyoutVisible && (
32+
<EuiFlyout onClose={() => setIsFlyoutVisible(false)}>
33+
<RuntimeFieldEditorFlyoutContent
34+
onSave={saveRuntimeField}
35+
onCancel={() => setIsFlyoutVisible(false)}
36+
docLinks={docLinksStart}
37+
defaultValue={/*optional runtime field to edit*/}
38+
/>
39+
</EuiFlyout>
40+
)}
41+
</>
42+
)
43+
}
44+
```
45+
46+
#### With the `core.overlays.openFlyout`
47+
48+
As an alternative you can open the flyout with the `core.overlays.openFlyout`. In this case you will need to wrap the editor with the `Provider` from the "kibana_react" plugin as it is a required dependency for the `<CodeEditor />` component.
49+
50+
```js
51+
import React, { useRef } from 'react';
52+
import { EuiButton } from '@elastic/eui';
53+
import { OverlayRef } from 'src/core/public';
54+
55+
import { createKibanaReactContext, toMountPoint } from '../../src/plugins/kibana_react/public';
56+
import { RuntimeFieldEditorFlyoutContent, RuntimeField } from '../runtime_fields/public';
57+
58+
const MyComponent = () => {
59+
// Access the core start service
60+
const { docLinksStart, overlays, uiSettings } = useCoreContext();
61+
const flyoutEditor = useRef<OverlayRef | null>(null);
62+
63+
const { openFlyout } = overlays;
64+
65+
const saveRuntimeField = useCallback((field: RuntimeField) => {
66+
// Do something with the field
67+
}, []);
68+
69+
const openRuntimeFieldEditor = useCallback(() => {
70+
const { Provider: KibanaReactContextProvider } = createKibanaReactContext({ uiSettings });
71+
72+
flyoutEditor.current = openFlyout(
73+
toMountPoint(
74+
<KibanaReactContextProvider>
75+
<RuntimeFieldEditorFlyoutContent
76+
onSave={saveRuntimeField}
77+
onCancel={() => flyoutEditor.current?.close()}
78+
docLinks={docLinksStart}
79+
defaultValue={defaultRuntimeField}
80+
/>
81+
</KibanaReactContextProvider>
82+
)
83+
);
84+
}, [openFlyout, saveRuntimeField, uiSettings]);
85+
86+
return (
87+
<>
88+
<EuiButton onClick={openRuntimeFieldEditor}>Create field</EuiButton>
89+
</>
90+
)
91+
}
92+
```
93+
94+
### Standalone component
95+
96+
```js
97+
import React, { useState } from 'react';
98+
import { EuiButton, EuiSpacer } from '@elastic/eui';
99+
import { RuntimeFieldEditor, RuntimeField, RuntimeFieldFormState } from '../runtime_fields/public';
100+
101+
const MyComponent = () => {
102+
const { docLinksStart } = useCoreContext(); // access the core start service
103+
const [runtimeFieldFormState, setRuntimeFieldFormState] = useState<RuntimeFieldFormState>({
104+
isSubmitted: false,
105+
isValid: undefined,
106+
submit: async() => Promise.resolve({ isValid: false, data: {} as RuntimeField })
107+
});
108+
109+
const { submit, isValid: isFormValid, isSubmitted } = runtimeFieldFormState;
110+
111+
const saveRuntimeField = useCallback(async () => {
112+
const { isValid, data } = await submit();
113+
if (isValid) {
114+
// Do something with the field (data)
115+
}
116+
}, [submit]);
117+
118+
return (
119+
<>
120+
<RuntimeFieldEditor
121+
onChange={setRuntimeFieldFormState}
122+
docLinks={docLinksStart}
123+
defaultValue={/*optional runtime field to edit*/}
124+
/>
125+
126+
<EuiSpacer />
127+
128+
<EuiButton
129+
onClick={saveRuntimeField}
130+
disabled={isSubmitted && !isFormValid}>
131+
Save field
132+
</EuiButton>
133+
</>
134+
)
135+
}
136+
```

x-pack/plugins/runtime_fields/public/__jest__/setup_environment.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ jest.mock('../../../../../src/plugins/kibana_react/public', () => {
1313
data-test-subj={props['data-test-subj'] || 'mockCodeEditor'}
1414
data-value={props.value}
1515
value={props.value}
16-
onChange={(syntheticEvent: any) => {
17-
props.onChange([syntheticEvent['0']]);
16+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
17+
props.onChange(e.target.value);
1818
}}
1919
/>
2020
);

x-pack/plugins/runtime_fields/public/components/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
export { RuntimeFieldForm } from './runtime_field_form';
7+
export { RuntimeFieldForm, FormState as RuntimeFieldFormState } from './runtime_field_form';
8+
9+
export { RuntimeFieldEditor } from './runtime_field_editor';
10+
11+
export { RuntimeFieldEditorFlyoutContent } from './runtime_field_editor_flyout_content';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export { RuntimeFieldEditor } from './runtime_field_editor';
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { act } from 'react-dom/test-utils';
7+
import { DocLinksStart } from 'src/core/public';
8+
9+
import '../../__jest__/setup_environment';
10+
import { registerTestBed, TestBed } from '../../test_utils';
11+
import { RuntimeField } from '../../types';
12+
import { RuntimeFieldForm, FormState } from '../runtime_field_form/runtime_field_form';
13+
import { RuntimeFieldEditor, Props } from './runtime_field_editor';
14+
15+
const setup = (props?: Props) =>
16+
registerTestBed(RuntimeFieldEditor, {
17+
memoryRouter: {
18+
wrapComponent: false,
19+
},
20+
})(props) as TestBed;
21+
22+
const docLinks: DocLinksStart = {
23+
ELASTIC_WEBSITE_URL: 'https://jestTest.elastic.co',
24+
DOC_LINK_VERSION: 'jest',
25+
links: {} as any,
26+
};
27+
28+
describe('Runtime field editor', () => {
29+
let testBed: TestBed;
30+
let onChange: jest.Mock<Props['onChange']> = jest.fn();
31+
32+
const lastOnChangeCall = (): FormState[] => onChange.mock.calls[onChange.mock.calls.length - 1];
33+
34+
beforeEach(() => {
35+
onChange = jest.fn();
36+
});
37+
38+
test('should render the <RuntimeFieldForm />', () => {
39+
testBed = setup({ docLinks });
40+
const { component } = testBed;
41+
42+
expect(component.find(RuntimeFieldForm).length).toBe(1);
43+
});
44+
45+
test('should accept a defaultValue and onChange prop to forward the form state', async () => {
46+
const defaultValue: RuntimeField = {
47+
name: 'foo',
48+
type: 'date',
49+
script: 'test=123',
50+
};
51+
testBed = setup({ onChange, defaultValue, docLinks });
52+
53+
expect(onChange).toHaveBeenCalled();
54+
55+
let lastState = lastOnChangeCall()[0];
56+
expect(lastState.isValid).toBe(undefined);
57+
expect(lastState.isSubmitted).toBe(false);
58+
expect(lastState.submit).toBeDefined();
59+
60+
let data;
61+
await act(async () => {
62+
({ data } = await lastState.submit());
63+
});
64+
expect(data).toEqual(defaultValue);
65+
66+
// Make sure that both isValid and isSubmitted state are now "true"
67+
lastState = lastOnChangeCall()[0];
68+
expect(lastState.isValid).toBe(true);
69+
expect(lastState.isSubmitted).toBe(true);
70+
});
71+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import React from 'react';
8+
import { DocLinksStart } from 'src/core/public';
9+
10+
import { RuntimeField } from '../../types';
11+
import { getLinks } from '../../lib';
12+
import { RuntimeFieldForm, Props as FormProps } from '../runtime_field_form/runtime_field_form';
13+
14+
export interface Props {
15+
docLinks: DocLinksStart;
16+
defaultValue?: RuntimeField;
17+
onChange?: FormProps['onChange'];
18+
}
19+
20+
export const RuntimeFieldEditor = ({ defaultValue, onChange, docLinks }: Props) => {
21+
const links = getLinks(docLinks);
22+
23+
return <RuntimeFieldForm links={links} defaultValue={defaultValue} onChange={onChange} />;
24+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export { RuntimeFieldEditorFlyoutContent } from './runtime_field_editor_flyout_content';

0 commit comments

Comments
 (0)