[Data views] Add pre-configuration options to runtime field editor fly-out (#136769)

* [Data views] Add pre-configuration options to runtime field editor fly-out

* fix test

* more polish

* update example app functional test

* fix functional test

* improve comment

* fix unexported public apis

* comments for public apis

* restrict runaway metrics changes

* more comments for public api

* fix fn test

* revert updates to api_docs

* more public api comments in data_view_field_editor

* fix api comments

* add public api export

* clean up FieldFormatConfig types

* cleanup

* allow checkbox to be visually checked
This commit is contained in:
Tim Sullivan 2022-07-22 10:30:01 -07:00 committed by GitHub
parent b8f41a0eea
commit 0b8b66f73f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 244 additions and 116 deletions

View file

@ -6,23 +6,28 @@
* Side Public License, v 1.
*/
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import {
DefaultItemAction,
EuiButton,
EuiCheckbox,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
EuiPage,
EuiPageHeader,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
EuiButton,
EuiInMemoryTable,
EuiPageHeader,
EuiText,
DefaultItemAction,
useGeneratedHtmlId,
} from '@elastic/eui';
import { AppMountParameters } from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { DataViewField } from '@kbn/data-views-plugin/public';
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
interface Props {
dataView?: DataView;
@ -33,6 +38,8 @@ const DataViewFieldEditorExample = ({ dataView, dataViewFieldEditor }: Props) =>
const [fields, setFields] = useState<DataViewField[]>(
dataView?.fields.getAll().filter((f) => !f.scripted) || []
);
const [preconfigured, setPreconfigured] = useState<boolean>(false);
const refreshFields = () => setFields(dataView?.fields.getAll().filter((f) => !f.scripted) || []);
const columns = [
{
@ -75,22 +82,42 @@ const DataViewFieldEditorExample = ({ dataView, dataViewFieldEditor }: Props) =>
},
];
const preconfigureId = useGeneratedHtmlId({ prefix: 'usePreconfigured' });
const content = dataView ? (
<>
<EuiText data-test-subj="dataViewTitle">Data view: {dataView.title}</EuiText>
<div>
<EuiButton
onClick={() =>
dataViewFieldEditor.openEditor({
ctx: { dataView },
onSave: refreshFields,
})
}
data-test-subj="addField"
>
Add field
</EuiButton>
</div>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={false}>
<EuiButton
onClick={() =>
dataViewFieldEditor.openEditor({
ctx: { dataView },
onSave: refreshFields,
fieldToCreate: preconfigured
? {
name: 'demotestfield',
type: 'boolean',
script: { source: 'emit(true)' }, // optional
customLabel: 'cool demo test field', // optional
format: { id: 'boolean' }, // optional
}
: undefined,
})
}
data-test-subj="addField"
>
Add field
</EuiButton>
</EuiFlexItem>
<EuiFlexItem data-test-subj="preconfiguredControlWrapper">
<EuiCheckbox
id={preconfigureId}
checked={preconfigured}
label="Use preconfigured options"
onChange={() => setPreconfigured(!preconfigured)}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiInMemoryTable<DataViewField>
items={fields}
columns={columns}

View file

@ -98,7 +98,7 @@ describe('<FieldEditor />', () => {
test('should accept a defaultValue and onChange prop to forward the form state', async () => {
const field = {
name: 'foo',
type: 'date',
type: 'date' as const,
script: { source: 'emit("hello")' },
};

View file

@ -34,7 +34,7 @@ export const setup = async (props?: Partial<Props>, deps?: Partial<Context>) =>
// Setup testbed
await act(async () => {
testBed = await registerTestBed(WithFieldEditorDependencies(FieldEditorFlyoutContent, deps), {
testBed = registerTestBed(WithFieldEditorDependencies(FieldEditorFlyoutContent, deps), {
memoryRouter: {
wrapComponent: false,
},

View file

@ -36,16 +36,16 @@ describe('<FieldEditorFlyoutContent />', () => {
expect(find('flyoutTitle').text()).toBe('Create field');
});
test('should allow a field to be provided', async () => {
test('should allow an existing field to be provided', async () => {
const field = {
name: 'foo',
type: 'ip',
type: 'ip' as const,
script: {
source: 'emit("hello world")',
},
};
const { find } = await setup({ field });
const { find } = await setup({ fieldToEdit: field });
expect(find('flyoutTitle').text()).toBe(`Edit field 'foo'`);
expect(find('nameField.input').props().value).toBe(field.name);
@ -53,15 +53,32 @@ describe('<FieldEditorFlyoutContent />', () => {
expect(find('scriptField').props().value).toBe(field.script.source);
});
test('should allow a new field to be created with initial configuration', async () => {
const fieldToCreate = {
name: 'demotestfield',
type: 'boolean' as const,
script: { source: 'emit(true)' },
customLabel: 'cool demo test field',
format: { id: 'boolean' },
};
const { find } = await setup({ fieldToCreate });
expect(find('flyoutTitle').text()).toBe(`Create field`);
expect(find('nameField.input').props().value).toBe(fieldToCreate.name);
expect(find('typeField').props().value).toBe(fieldToCreate.type);
expect(find('scriptField').props().value).toBe(fieldToCreate.script.source);
});
test('should accept an "onSave" prop', async () => {
const field = {
name: 'foo',
type: 'date',
type: 'date' as const,
script: { source: 'test=123' },
};
const onSave: jest.Mock<Props['onSave']> = jest.fn();
const { find, actions } = await setup({ onSave, field });
const { find, actions } = await setup({ onSave, fieldToEdit: field });
await act(async () => {
find('fieldSaveButton').simulate('click');

View file

@ -164,7 +164,7 @@ export const setup = async (props?: Partial<Props>, deps?: Partial<Context>) =>
// Setup testbed
await act(async () => {
testBed = await registerTestBed(WithFieldEditorDependencies(FieldEditorFlyoutContent, deps), {
testBed = registerTestBed(WithFieldEditorDependencies(FieldEditorFlyoutContent, deps), {
memoryRouter: {
wrapComponent: false,
},

View file

@ -216,7 +216,7 @@ describe('Field editor Preview panel', () => {
test('should **not** display an empty prompt editing a document with a script', async () => {
const field = {
name: 'foo',
type: 'ip',
type: 'ip' as const,
script: {
source: 'emit("hello world")',
},
@ -225,7 +225,7 @@ describe('Field editor Preview panel', () => {
// We open the editor with a field to edit the empty prompt should not be there
// as we have a script and we'll load the preview.
await act(async () => {
testBed = await setup({ field });
testBed = await setup({ fieldToEdit: field });
});
const { exists, component } = testBed;
@ -237,7 +237,7 @@ describe('Field editor Preview panel', () => {
test('should **not** display an empty prompt editing a document with format defined', async () => {
const field = {
name: 'foo',
type: 'ip',
type: 'ip' as const,
format: {
id: 'upper',
params: {},
@ -245,7 +245,7 @@ describe('Field editor Preview panel', () => {
};
await act(async () => {
testBed = await setup({ field });
testBed = await setup({ fieldToEdit: field });
});
const { exists, component } = testBed;

View file

@ -64,7 +64,7 @@ export interface FieldFormInternal extends Omit<Field, 'type' | 'internalType'>
}
export interface Props {
/** Optional field to edit */
/** Optional field to edit or preselected field to create */
field?: Field;
/** Handler to receive state changes updates */
onChange?: (state: FieldEditorFormState) => void;

View file

@ -5,14 +5,14 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React, { useState, useEffect, useRef } from 'react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { UseField, useFormData, ES_FIELD_TYPES, useFormContext } from '../../../shared_imports';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
import React, { useEffect, useRef, useState } from 'react';
import { ES_FIELD_TYPES, UseField, useFormContext, useFormData } from '../../../shared_imports';
import { useFieldEditorContext } from '../../field_editor_context';
import { FormatSelectEditor } from '../../field_format_editor';
import type { FieldFormInternal } from '../field_editor';
import type { FieldFormatConfig } from '../../../types';
export const FormatField = () => {
const { dataView, uiSettings, fieldFormats, fieldFormatEditors } = useFieldEditorContext();
@ -44,7 +44,7 @@ export const FormatField = () => {
}, [type, getFields]);
return (
<UseField<FieldFormatConfig | undefined> path="format">
<UseField<SerializedFieldFormat | undefined> path="format">
{({ setValue, errors, value }) => {
return (
<>

View file

@ -6,25 +6,24 @@
* Side Public License, v 1.
*/
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiTitle,
EuiButton,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiButtonEmpty,
EuiButton,
EuiText,
EuiTitle,
} from '@elastic/eui';
import type { Field } from '../types';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { euiFlyoutClassname } from '../constants';
import { FlyoutPanels } from './flyout_panels';
import { useFieldEditorContext } from './field_editor_context';
import { FieldEditor, FieldEditorFormState } from './field_editor/field_editor';
import { FieldPreview, useFieldPreviewContext } from './preview';
import type { Field } from '../types';
import { ModifiedFieldModal, SaveFieldTypeOrNameChangedModal } from './confirm_modals';
import { FieldEditor, FieldEditorFormState } from './field_editor/field_editor';
import { useFieldEditorContext } from './field_editor_context';
import { FlyoutPanels } from './flyout_panels';
import { FieldPreview, useFieldPreviewContext } from './preview';
const i18nTexts = {
cancelButtonLabel: i18n.translate('indexPatternFieldEditor.editor.flyoutCancelButtonLabel', {
@ -50,7 +49,9 @@ export interface Props {
*/
onCancel: () => void;
/** Optional field to process */
field?: Field;
fieldToEdit?: Field;
/** Optional preselected configuration for new field */
fieldToCreate?: Field;
isSavingField: boolean;
/** Handler to call when the component mounts.
* We will pass "up" data that the parent component might need
@ -59,14 +60,15 @@ export interface Props {
}
const FieldEditorFlyoutContentComponent = ({
field,
fieldToEdit,
fieldToCreate,
onSave,
onCancel,
isSavingField,
onMounted,
}: Props) => {
const isMounted = useRef(false);
const isEditingExistingField = !!field;
const isEditingExistingField = !!fieldToEdit;
const { dataView } = useFieldEditorContext();
const {
panel: { isVisible: isPanelVisible },
@ -75,9 +77,9 @@ const FieldEditorFlyoutContentComponent = ({
const [formState, setFormState] = useState<FieldEditorFormState>({
isSubmitted: false,
isSubmitting: false,
isValid: field ? true : undefined,
submit: field
? async () => ({ isValid: true, data: field })
isValid: fieldToEdit ? true : undefined,
submit: fieldToEdit
? async () => ({ isValid: true, data: fieldToEdit })
: async () => ({ isValid: false, data: {} as Field }),
});
@ -106,8 +108,8 @@ const FieldEditorFlyoutContentComponent = ({
}
if (isValid) {
const nameChange = field?.name !== data.name;
const typeChange = field?.type !== data.type;
const nameChange = fieldToEdit?.name !== data.name;
const typeChange = fieldToEdit?.type !== data.type;
if (isEditingExistingField && (nameChange || typeChange)) {
setModalVisibility({
@ -118,7 +120,7 @@ const FieldEditorFlyoutContentComponent = ({
onSave(data);
}
}
}, [onSave, submit, field, isEditingExistingField]);
}, [onSave, submit, fieldToEdit, isEditingExistingField]);
const onClickCancel = useCallback(() => {
const canClose = canCloseValidator();
@ -132,7 +134,7 @@ const FieldEditorFlyoutContentComponent = ({
if (modalVisibility.confirmChangeNameOrType) {
return (
<SaveFieldTypeOrNameChangedModal
fieldName={field?.name!}
fieldName={fieldToEdit?.name!}
onConfirm={async () => {
const { data } = await submit();
onSave(data);
@ -196,12 +198,12 @@ const FieldEditorFlyoutContentComponent = ({
<FlyoutPanels.Header>
<EuiTitle data-test-subj="flyoutTitle">
<h2>
{field ? (
{fieldToEdit ? (
<FormattedMessage
id="indexPatternFieldEditor.editor.flyoutEditFieldTitle"
defaultMessage="Edit field '{fieldName}'"
values={{
fieldName: field.name,
fieldName: fieldToEdit.name,
}}
/>
) : (
@ -226,7 +228,7 @@ const FieldEditorFlyoutContentComponent = ({
</FlyoutPanels.Header>
<FieldEditor
field={field}
field={fieldToEdit ?? fieldToCreate}
onChange={setFormState}
onFormModifiedChange={setIsFormModified}
/>

View file

@ -43,7 +43,9 @@ export interface Props {
/** The Kibana field type of the field to create or edit (default: "runtime") */
fieldTypeToProcess: InternalFieldType;
/** Optional field to edit */
field?: DataViewField;
fieldToEdit?: DataViewField;
/** Optional initial configuration for new field */
fieldToCreate?: Field;
/** Services */
dataViews: DataViewsPublicPluginStart;
notifications: NotificationsStart;
@ -64,7 +66,8 @@ export interface Props {
*/
export const FieldEditorFlyoutContentContainer = ({
field,
fieldToEdit,
fieldToCreate,
onSave,
onCancel,
onMounted,
@ -80,7 +83,6 @@ export const FieldEditorFlyoutContentContainer = ({
fieldFormats,
uiSettings,
}: Props) => {
const fieldToEdit = deserializeField(dataView, field);
const [isSaving, setIsSaving] = useState(false);
const { fields } = dataView;
@ -92,7 +94,7 @@ export const FieldEditorFlyoutContentContainer = ({
fields
.filter((fld) => {
const isFieldBeingEdited = field?.name === fld.name;
const isFieldBeingEdited = fieldToEdit?.name === fld.name;
return !isFieldBeingEdited && fld.isMapped;
})
.forEach((fld) => {
@ -103,7 +105,7 @@ export const FieldEditorFlyoutContentContainer = ({
});
return existing;
}, [fields, field]);
}, [fields, fieldToEdit]);
const services = useMemo(
() => ({
@ -126,8 +128,8 @@ export const FieldEditorFlyoutContentContainer = ({
// eslint-disable-next-line no-empty
} catch {}
// rename an existing runtime field
if (field?.name && field.name !== updatedField.name) {
dataView.removeRuntimeField(field.name);
if (fieldToEdit?.name && fieldToEdit.name !== updatedField.name) {
dataView.removeRuntimeField(fieldToEdit.name);
}
dataView.addRuntimeField(updatedField.name, {
@ -184,7 +186,15 @@ export const FieldEditorFlyoutContentContainer = ({
setIsSaving(false);
}
},
[onSave, dataView, dataViews, notifications, fieldTypeToProcess, field?.name, usageCollection]
[
onSave,
dataView,
dataViews,
notifications,
fieldTypeToProcess,
fieldToEdit?.name,
usageCollection,
]
);
return (
@ -204,7 +214,8 @@ export const FieldEditorFlyoutContentContainer = ({
onSave={saveField}
onCancel={onCancel}
onMounted={onMounted}
field={fieldToEdit}
fieldToCreate={fieldToCreate}
fieldToEdit={deserializeField(dataView, fieldToEdit)}
isSavingField={isSaving}
/>
</FieldPreviewProvider>

View file

@ -10,14 +10,17 @@ import { EuiCode, EuiFormRow, EuiSelect } from '@elastic/eui';
import { CoreStart } from '@kbn/core/public';
import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/data-plugin/public';
import { DataView } from '@kbn/data-views-plugin/public';
import type { FieldFormatInstanceType, FieldFormatParams } from '@kbn/field-formats-plugin/common';
import type {
FieldFormatInstanceType,
FieldFormatParams,
SerializedFieldFormat,
} from '@kbn/field-formats-plugin/common';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { castEsToKbnFieldTypeName } from '@kbn/field-types';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { PureComponent } from 'react';
import { FormatEditorServiceStart } from '../../service';
import { FieldFormatConfig } from '../../types';
import { FormatEditor } from './format_editor';
export interface FormatSelectEditorProps {
@ -26,9 +29,9 @@ export interface FormatSelectEditorProps {
fieldFormatEditors: FormatEditorServiceStart['fieldFormatEditors'];
fieldFormats: FieldFormatsStart;
uiSettings: CoreStart['uiSettings'];
onChange: (change?: FieldFormatConfig) => void;
onChange: (change?: SerializedFieldFormat) => void;
onError: (error?: string) => void;
value?: FieldFormatConfig;
value?: SerializedFieldFormat;
}
interface FieldTypeFormat {

View file

@ -5,10 +5,11 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import type { RuntimeType, RuntimeField } from '../../shared_imports';
import type { FieldFormatConfig, RuntimeFieldPainlessError } from '../../types';
import { SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
import React from 'react';
import type { RuntimeField, RuntimeType } from '../../shared_imports';
import type { RuntimeFieldPainlessError } from '../../types';
export type From = 'cluster' | 'custom';
@ -54,7 +55,7 @@ export interface Params {
index: string | null;
type: RuntimeType | null;
script: Required<RuntimeField>['script'] | null;
format: FieldFormatConfig | null;
format: SerializedFieldFormat | null;
document: { [key: string]: unknown } | null;
}

View file

@ -21,6 +21,7 @@
import { IndexPatternFieldEditorPlugin } from './plugin';
export type {
Field,
PluginSetup as IndexPatternFieldEditorSetup,
PluginStart as IndexPatternFieldEditorStart,
} from './types';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { monaco } from '@kbn/monaco';
import { DataViewField, DataView } from '../shared_imports';
import { DataViewField, DataView, RuntimeType } from '../shared_imports';
import type { Field, RuntimeFieldPainlessError } from '../types';
export const deserializeField = (dataView: DataView, field?: DataViewField): Field | undefined => {
@ -16,7 +16,7 @@ export const deserializeField = (dataView: DataView, field?: DataViewField): Fie
return {
name: field.name,
type: field?.esTypes ? field.esTypes[0] : 'keyword',
type: field?.esTypes ? (field.esTypes[0] as RuntimeType) : ('keyword' as const),
script: field.runtimeField ? field.runtimeField.script : undefined,
customLabel: field.customLabel,
popularity: field.count,

View file

@ -6,33 +6,47 @@
* Side Public License, v 1.
*/
import React from 'react';
import { CoreStart, OverlayRef } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
import {
createKibanaReactContext,
toMountPoint,
DataViewField,
import React from 'react';
import { FieldEditorLoader } from './components/field_editor_loader';
import { euiFlyoutClassname } from './constants';
import type { ApiService } from './lib/api';
import type {
DataPublicPluginStart,
DataView,
UsageCollectionStart,
DataViewField,
DataViewsPublicPluginStart,
FieldFormatsStart,
RuntimeType,
UsageCollectionStart,
} from './shared_imports';
import { createKibanaReactContext, toMountPoint } from './shared_imports';
import type { CloseEditor, Field, InternalFieldType, PluginStart } from './types';
import type { PluginStart, InternalFieldType, CloseEditor } from './types';
import type { ApiService } from './lib/api';
import { euiFlyoutClassname } from './constants';
import { FieldEditorLoader } from './components/field_editor_loader';
/**
* Options for opening the field editor
* @public
*/
export interface OpenFieldEditorOptions {
/**
* context containing the data view the field belongs to
*/
ctx: {
dataView: DataView;
};
/**
* action to take after field is saved
*/
onSave?: (field: DataViewField) => void;
/**
* field to edit, for existing field
*/
fieldName?: string;
/**
* pre-selectable options for new field creation
*/
fieldToCreate?: Field;
}
interface Dependencies {
@ -75,7 +89,8 @@ export const getFieldEditorOpener =
const openEditor = ({
onSave,
fieldName,
fieldName: fieldNameToEdit,
fieldToCreate,
ctx: { dataView },
}: OpenFieldEditorOptions): CloseEditor => {
const closeEditor = () => {
@ -93,24 +108,24 @@ export const getFieldEditorOpener =
}
};
const field = fieldName ? dataView.getFieldByName(fieldName) : undefined;
const fieldToEdit = fieldNameToEdit ? dataView.getFieldByName(fieldNameToEdit) : undefined;
if (fieldName && !field) {
if (fieldNameToEdit && !fieldToEdit) {
const err = i18n.translate('indexPatternFieldEditor.noSuchFieldName', {
defaultMessage: "Field named '{fieldName}' not found on index pattern",
values: { fieldName },
values: { fieldName: fieldNameToEdit },
});
notifications.toasts.addDanger(err);
return closeEditor;
}
const isNewRuntimeField = !fieldName;
const isNewRuntimeField = !fieldNameToEdit;
const isExistingRuntimeField =
field &&
field.runtimeField &&
!field.isMapped &&
fieldToEdit &&
fieldToEdit.runtimeField &&
!fieldToEdit.isMapped &&
// treat composite field instances as mapped fields for field editing purposes
field.runtimeField.type !== ('composite' as RuntimeType);
fieldToEdit.runtimeField.type !== ('composite' as RuntimeType);
const fieldTypeToProcess: InternalFieldType =
isNewRuntimeField || isExistingRuntimeField ? 'runtime' : 'concrete';
@ -122,7 +137,8 @@ export const getFieldEditorOpener =
onCancel={closeEditor}
onMounted={onMounted}
docLinks={docLinks}
field={field}
fieldToEdit={fieldToEdit}
fieldToCreate={fieldToCreate}
fieldTypeToProcess={fieldTypeToProcess}
dataView={dataView}
search={search}
@ -150,7 +166,7 @@ export const getFieldEditorOpener =
: i18n.translate('indexPatternFieldEditor.editField.flyoutAriaLabel', {
defaultMessage: 'Edit {fieldName} field',
values: {
fieldName,
fieldName: fieldNameToEdit,
},
}),
onClose: (flyout) => {

View file

@ -28,7 +28,7 @@ export class IndexPatternFieldEditorPlugin
};
}
public start(core: CoreStart, plugins: StartPlugins) {
public start(core: CoreStart, plugins: StartPlugins): PluginStart {
const { fieldFormatEditors } = this.formatEditorService.start();
const { http } = core;
const { data, usageCollection, dataViews, fieldFormats } = plugins;

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { SerializableRecord } from '@kbn/utility-types';
import { FieldSpec } from '@kbn/data-views-plugin/public';
import { FunctionComponent } from 'react';
import { DeleteFieldProviderProps } from './components';
import { OpenFieldDeleteModalOptions } from './open_delete_modal';
@ -21,11 +21,22 @@ import {
UsageCollectionStart,
} from './shared_imports';
/**
* Public setup contract of data view field editor
* @public
*/
export interface PluginSetup {
fieldFormatEditors: FormatEditorServiceSetup['fieldFormatEditors'];
}
/**
* Public start contract of data view field editor
* @public
*/
export interface PluginStart {
/**
* method to open the data view field editor fly-out
*/
openEditor(options: OpenFieldEditorOptions): () => void;
openDeleteModal(options: OpenFieldDeleteModalOptions): () => void;
fieldFormatEditors: FormatEditorServiceStart['fieldFormatEditors'];
@ -47,18 +58,35 @@ export interface StartPlugins {
export type InternalFieldType = 'concrete' | 'runtime';
/**
* The data model for the field editor
* @public
*/
export interface Field {
name: string;
type: RuntimeField['type'] | string;
/**
* name / path used for the field
*/
name: FieldSpec['name'];
/**
* ES type
*/
type: RuntimeType;
/**
* source of the runtime field script
*/
script?: RuntimeField['script'];
customLabel?: string;
/**
* custom label for display
*/
customLabel?: FieldSpec['customLabel'];
/**
* custom popularity
*/
popularity?: number;
format?: FieldFormatConfig;
}
export interface FieldFormatConfig {
id: string;
params?: SerializableRecord;
/**
* configuration of the field format
*/
format?: FieldSpec['format'];
}
export interface EsRuntimeField {

View file

@ -6,19 +6,41 @@
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../plugin_functional/services';
// eslint-disable-next-line import/no-default-export
export default function ({ getService }: PluginFunctionalProviderContext) {
const testSubjects = getService('testSubjects');
const find = getService('find');
describe('', () => {
it('finds a data view', async () => {
await testSubjects.existOrFail('dataViewTitle');
});
it('opens the field editor', async () => {
await testSubjects.click('addField');
await testSubjects.existOrFail('flyoutTitle');
await testSubjects.click('closeFlyoutButton');
});
it('uses preconfigured options for a new field', async () => {
// find the checkbox label and click it - `testSubjects.setCheckbox()` is not working for our checkbox
const controlWrapper = await testSubjects.find('preconfiguredControlWrapper');
const control = await find.descendantDisplayedByCssSelector('label', controlWrapper);
await control.click();
await testSubjects.click('addField');
await testSubjects.existOrFail('flyoutTitle');
const nameField = await testSubjects.find('nameField');
const nameInput = await find.descendantDisplayedByCssSelector(
'[data-test-subj=input]',
nameField
);
expect(await nameInput.getAttribute('value')).to.equal('demotestfield');
});
});
}