[data view field editor] Allow editing of DataViewLazy (#186348)

## Summary

Data view field editor will now allow editing of fields when provided
with a DataViewLazy object. Previously it required a DataView object.
This change makes it easier for API consumers to move from DataView to
DataViewLazy usage.

Internally the data view field editor still uses DataView objects since
some of the validation code expects a complete field list. The
validation code would need to be rewritten to assume incompete field
lists. There is the potential for a performance hit when loading a large
field list. After the initial load it will be loaded from the browser
cache which should be performant.

Part of https://github.com/elastic/kibana/issues/178926
This commit is contained in:
Matthew Kime 2024-06-22 10:54:30 -05:00 committed by GitHub
parent 772ace62d7
commit 74a202a79a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 46 additions and 39 deletions

View file

@ -650,10 +650,10 @@ export const UnifiedDataTable = ({
const editField = useMemo(
() =>
onFieldEdited
? (fieldName: string) => {
? async (fieldName: string) => {
closeFieldEditor.current =
onFieldEdited &&
services?.dataViewFieldEditor?.openEditor({
(await services?.dataViewFieldEditor?.openEditor({
ctx: {
dataView,
},
@ -661,7 +661,7 @@ export const UnifiedDataTable = ({
onSave: async () => {
await onFieldEdited();
},
});
}));
}
: undefined,
[dataView, onFieldEdited, services?.dataViewFieldEditor]

View file

@ -163,8 +163,8 @@ const UnifiedFieldListSidebarContainer = memo(
const editField = useMemo(
() =>
dataView && dataViewFieldEditor && searchMode === 'documents' && canEditDataView
? (fieldName?: string) => {
const ref = dataViewFieldEditor.openEditor({
? async (fieldName?: string) => {
const ref = await dataViewFieldEditor.openEditor({
ctx: {
dataView,
},

View file

@ -15,13 +15,14 @@ import { euiFlyoutClassname } from './constants';
import type { ApiService } from './lib/api';
import type {
DataPublicPluginStart,
DataView,
UsageCollectionStart,
RuntimeType,
DataViewsPublicPluginStart,
FieldFormatsStart,
DataViewField,
DataViewLazy,
} from './shared_imports';
import { DataView } from './shared_imports';
import { createKibanaReactContext } from './shared_imports';
import type { CloseEditor, Field, InternalFieldType, PluginStart } from './types';
@ -34,7 +35,7 @@ export interface OpenFieldEditorOptions {
* context containing the data view the field belongs to
*/
ctx: {
dataView: DataView;
dataView: DataView | DataViewLazy;
};
/**
* action to take after field is saved
@ -72,7 +73,7 @@ export const getFieldEditorOpener =
usageCollection,
apiService,
}: Dependencies) =>
(options: OpenFieldEditorOptions): CloseEditor => {
async (options: OpenFieldEditorOptions): Promise<CloseEditor> => {
const { uiSettings, overlays, docLinks, notifications, settings, theme } = core;
const { Provider: KibanaReactContextProvider } = createKibanaReactContext({
uiSettings,
@ -91,12 +92,12 @@ export const getFieldEditorOpener =
canCloseValidator.current = args.canCloseValidator;
};
const openEditor = ({
const openEditor = async ({
onSave,
fieldName: fieldNameToEdit,
fieldToCreate,
ctx: { dataView },
}: OpenFieldEditorOptions): CloseEditor => {
ctx: { dataView: dataViewLazyOrNot },
}: OpenFieldEditorOptions): Promise<CloseEditor> => {
const closeEditor = () => {
if (overlayRef) {
overlayRef.close();
@ -113,7 +114,7 @@ export const getFieldEditorOpener =
};
const getRuntimeField = (name: string) => {
const fld = dataView.getAllRuntimeFields()[name];
const fld = dataViewLazyOrNot.getAllRuntimeFields()[name];
return {
name,
runtimeField: fld,
@ -129,6 +130,11 @@ export const getFieldEditorOpener =
};
};
const dataView =
dataViewLazyOrNot instanceof DataView
? dataViewLazyOrNot
: await dataViews.toDataView(dataViewLazyOrNot);
const dataViewField = fieldNameToEdit
? dataView.getFieldByName(fieldNameToEdit) || getRuntimeField(fieldNameToEdit)
: undefined;

View file

@ -63,7 +63,7 @@ describe('DataViewFieldEditorPlugin', () => {
};
const { openEditor } = plugin.start(coreStartMocked, pluginStart);
openEditor({ onSave: onSaveSpy, ctx: { dataView: {} as any } });
await openEditor({ onSave: onSaveSpy, ctx: { dataView: {} as any } });
expect(openFlyout).toHaveBeenCalled();
@ -82,7 +82,7 @@ describe('DataViewFieldEditorPlugin', () => {
test('should return a handler to close the flyout', async () => {
const { openEditor } = plugin.start(coreStart, pluginStart);
const closeEditorHandler = openEditor({ onSave: noop, ctx: { dataView: {} as any } });
const closeEditorHandler = await openEditor({ onSave: noop, ctx: { dataView: {} as any } });
expect(typeof closeEditorHandler).toBe('function');
});

View file

@ -8,11 +8,8 @@
export type { DataPublicPluginStart } from '@kbn/data-plugin/public';
export type {
DataViewsPublicPluginStart,
DataView,
DataViewField,
} from '@kbn/data-views-plugin/public';
export type { DataViewsPublicPluginStart, DataViewField } from '@kbn/data-views-plugin/public';
export { DataView } from '@kbn/data-views-plugin/public';
export type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
export type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
@ -24,6 +21,7 @@ export type {
RuntimeFieldSubField,
RuntimeFieldSubFields,
RuntimePrimitiveTypes,
DataViewLazy,
} from '@kbn/data-views-plugin/common';
export { KBN_FIELD_TYPES, ES_FIELD_TYPES } from '@kbn/data-plugin/common';

View file

@ -38,12 +38,12 @@ export interface PluginStart {
/**
* Method to open the data view field editor fly-out
*/
openEditor(options: OpenFieldEditorOptions): () => void;
openEditor(options: OpenFieldEditorOptions): Promise<CloseEditor>;
/**
* Method to open the data view field delete fly-out
* @param options Configuration options for the fly-out
*/
openDeleteModal(options: OpenFieldDeleteModalOptions): () => void;
openDeleteModal(options: OpenFieldDeleteModalOptions): CloseEditor;
fieldFormatEditors: FormatEditorServiceStart['fieldFormatEditors'];
/**
* Convenience method for user permissions checks

View file

@ -308,8 +308,8 @@ export const Tabs: React.FC<TabsProps> = ({
}, []);
const openFieldEditor = useCallback(
(fieldName?: string) => {
closeEditorHandler.current = dataViewFieldEditor.openEditor({
async (fieldName?: string) => {
closeEditorHandler.current = await dataViewFieldEditor.openEditor({
ctx: {
dataView: indexPattern,
},

View file

@ -40,6 +40,7 @@ const createStartContract = (): Start => {
getIdsWithTitle: jest.fn(),
getFieldsForIndexPattern: jest.fn(),
create: jest.fn().mockReturnValue(Promise.resolve({})),
toDataView: jest.fn().mockReturnValue(Promise.resolve({})),
} as unknown as jest.Mocked<DataViewsContract>;
};

View file

@ -92,7 +92,7 @@ export const DiscoverTopNav = ({
? async (fieldName?: string, uiAction: 'edit' | 'add' = 'edit') => {
if (dataView?.id) {
const dataViewInstance = await data.dataViews.get(dataView.id);
closeFieldEditor.current = dataViewFieldEditor.openEditor({
closeFieldEditor.current = await dataViewFieldEditor.openEditor({
ctx: {
dataView: dataViewInstance,
},

View file

@ -123,8 +123,8 @@ export function getActions(
),
type: 'icon',
icon: 'indexEdit',
onClick: (item: FieldVisConfig) => {
dataViewEditorRef.current = services.dataViewFieldEditor?.openEditor({
onClick: async (item: FieldVisConfig) => {
dataViewEditorRef.current = await services.dataViewFieldEditor?.openEditor({
ctx: { dataView },
fieldName: item.fieldName,
onSave: refreshPage,

View file

@ -48,8 +48,8 @@ export function DataVisualizerDataViewManagement(props: DataVisualizerDataViewMa
return null;
}
const addField = () => {
closeFieldEditor.current = dataViewFieldEditor.openEditor({
const addField = async () => {
closeFieldEditor.current = await dataViewFieldEditor.openEditor({
ctx: {
dataView: currentDataView,
},

View file

@ -913,7 +913,7 @@ export const LensTopNavMenu = ({
? async (fieldName?: string, _uiAction: 'edit' | 'add' = 'edit') => {
if (currentIndexPattern?.id) {
const indexPatternInstance = await data.dataViews.get(currentIndexPattern?.id);
closeFieldEditor.current = dataViewFieldEditor.openEditor({
closeFieldEditor.current = await dataViewFieldEditor.openEditor({
ctx: {
dataView: indexPatternInstance,
},

View file

@ -307,7 +307,7 @@ export const InnerFormBasedDataPanel = function InnerFormBasedDataPanel({
editPermission
? async (fieldName?: string, uiAction: 'edit' | 'add' = 'edit') => {
const indexPatternInstance = await dataViews.get(currentIndexPattern?.id);
closeFieldEditor.current = indexPatternFieldEditor.openEditor({
closeFieldEditor.current = await indexPatternFieldEditor.openEditor({
ctx: {
dataView: indexPatternInstance,
},

View file

@ -38,8 +38,8 @@ export const DataViewPicker = memo(() => {
const { dataViewId } = useSelector(sourcererAdapterSelector);
const createNewDataView = useCallback(() => {
closeDataViewEditor.current = dataViewEditor.openEditor({
const createNewDataView = useCallback(async () => {
closeDataViewEditor.current = await dataViewEditor.openEditor({
// eslint-disable-next-line no-console
onSave: () => console.log('new data view saved'),
allowAdHocDataView: true,
@ -58,7 +58,7 @@ export const DataViewPicker = memo(() => {
}
const dataViewInstance = await data.dataViews.get(dataViewId);
closeFieldEditor.current = dataViewFieldEditor.openEditor({
closeFieldEditor.current = await dataViewFieldEditor.openEditor({
ctx: {
dataView: dataViewInstance,
},

View file

@ -164,7 +164,7 @@ describe('useFieldBrowserOptions', () => {
it('should dispatch the proper action when a new field is saved', async () => {
let onSave: ((field: DataViewField[]) => void) | undefined;
useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView);
useKibanaMock().services.dataViewFieldEditor.openEditor = (options) => {
useKibanaMock().services.dataViewFieldEditor.openEditor = async (options) => {
onSave = options.onSave;
return () => {};
};
@ -198,7 +198,7 @@ describe('useFieldBrowserOptions', () => {
it('should dispatch the proper actions when a field is edited', async () => {
let onSave: ((field: DataViewField[]) => void) | undefined;
useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView);
useKibanaMock().services.dataViewFieldEditor.openEditor = (options) => {
useKibanaMock().services.dataViewFieldEditor.openEditor = async (options) => {
onSave = options.onSave;
return () => {};
};
@ -266,7 +266,7 @@ describe('useFieldBrowserOptions', () => {
it("should store 'closeEditor' in the actions ref when editor is open by create button", async () => {
const mockCloseEditor = jest.fn();
useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView);
useKibanaMock().services.dataViewFieldEditor.openEditor = () => mockCloseEditor;
useKibanaMock().services.dataViewFieldEditor.openEditor = async () => mockCloseEditor;
const editorActionsRef: FieldEditorActionsRef = React.createRef();
@ -280,6 +280,7 @@ describe('useFieldBrowserOptions', () => {
expect(editorActionsRef?.current).toBeNull();
getByRole('button').click();
await runAllPromises();
expect(mockCloseEditor).not.toHaveBeenCalled();
expect(editorActionsRef?.current?.closeEditor).toBeDefined();
@ -293,7 +294,7 @@ describe('useFieldBrowserOptions', () => {
it("should store 'closeEditor' in the actions ref when editor is open by edit button", async () => {
const mockCloseEditor = jest.fn();
useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView);
useKibanaMock().services.dataViewFieldEditor.openEditor = () => mockCloseEditor;
useKibanaMock().services.dataViewFieldEditor.openEditor = async () => mockCloseEditor;
const editorActionsRef: FieldEditorActionsRef = React.createRef();
@ -311,6 +312,7 @@ describe('useFieldBrowserOptions', () => {
expect(editorActionsRef?.current).toBeNull();
getByTestId('actionEditRuntimeField').click();
await runAllPromises();
expect(mockCloseEditor).not.toHaveBeenCalled();
expect(editorActionsRef?.current?.closeEditor).toBeDefined();

View file

@ -81,9 +81,9 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({
}, [selectedDataViewId, missingPatterns, dataViews]);
const openFieldEditor = useCallback<OpenFieldEditor>(
(fieldName) => {
async (fieldName) => {
if (dataView && selectedDataViewId) {
const closeFieldEditor = dataViewFieldEditor.openEditor({
const closeFieldEditor = await dataViewFieldEditor.openEditor({
ctx: { dataView },
fieldName,
onSave: async (savedFields: DataViewField[]) => {