From 74a202a79a116d3b56396783c465522bc56fc59c Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Sat, 22 Jun 2024 10:54:30 -0500 Subject: [PATCH] [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 --- .../src/components/data_table.tsx | 6 +++--- .../field_list_sidebar_container.tsx | 4 ++-- .../public/open_editor.tsx | 20 ++++++++++++------- .../public/plugin.test.tsx | 4 ++-- .../public/shared_imports.ts | 8 +++----- .../data_view_field_editor/public/types.ts | 4 ++-- .../edit_index_pattern/tabs/tabs.tsx | 4 ++-- src/plugins/data_views/public/mocks.ts | 1 + .../components/top_nav/discover_topnav.tsx | 2 +- .../field_data_row/action_menu/actions.ts | 4 ++-- .../data_view_management.tsx | 4 ++-- .../lens/public/app_plugin/lens_top_nav.tsx | 2 +- .../datasources/form_based/datapanel.tsx | 2 +- .../components/dataview_picker/index.tsx | 6 +++--- .../components/fields_browser/index.test.tsx | 10 ++++++---- .../components/fields_browser/index.tsx | 4 ++-- 16 files changed, 46 insertions(+), 39 deletions(-) diff --git a/packages/kbn-unified-data-table/src/components/data_table.tsx b/packages/kbn-unified-data-table/src/components/data_table.tsx index 3a64947fe39cad..1262619401ab60 100644 --- a/packages/kbn-unified-data-table/src/components/data_table.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table.tsx @@ -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] diff --git a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx index 7bf3317aabe22d..68b3380b712ad0 100644 --- a/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx +++ b/packages/kbn-unified-field-list/src/containers/unified_field_list_sidebar/field_list_sidebar_container.tsx @@ -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, }, diff --git a/src/plugins/data_view_field_editor/public/open_editor.tsx b/src/plugins/data_view_field_editor/public/open_editor.tsx index cbd9650f822455..e6caeacdfa7d96 100644 --- a/src/plugins/data_view_field_editor/public/open_editor.tsx +++ b/src/plugins/data_view_field_editor/public/open_editor.tsx @@ -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 => { 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 => { 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; diff --git a/src/plugins/data_view_field_editor/public/plugin.test.tsx b/src/plugins/data_view_field_editor/public/plugin.test.tsx index a527f3b09de836..317faa7bf5c639 100644 --- a/src/plugins/data_view_field_editor/public/plugin.test.tsx +++ b/src/plugins/data_view_field_editor/public/plugin.test.tsx @@ -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'); }); diff --git a/src/plugins/data_view_field_editor/public/shared_imports.ts b/src/plugins/data_view_field_editor/public/shared_imports.ts index 62fad495cd22bc..8042b1594c618f 100644 --- a/src/plugins/data_view_field_editor/public/shared_imports.ts +++ b/src/plugins/data_view_field_editor/public/shared_imports.ts @@ -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'; diff --git a/src/plugins/data_view_field_editor/public/types.ts b/src/plugins/data_view_field_editor/public/types.ts index 3688f79c16689f..46c0725d78339e 100644 --- a/src/plugins/data_view_field_editor/public/types.ts +++ b/src/plugins/data_view_field_editor/public/types.ts @@ -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; /** * 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 diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/tabs.tsx index b72ef13a6cfdca..2cf6916f25868a 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -308,8 +308,8 @@ export const Tabs: React.FC = ({ }, []); const openFieldEditor = useCallback( - (fieldName?: string) => { - closeEditorHandler.current = dataViewFieldEditor.openEditor({ + async (fieldName?: string) => { + closeEditorHandler.current = await dataViewFieldEditor.openEditor({ ctx: { dataView: indexPattern, }, diff --git a/src/plugins/data_views/public/mocks.ts b/src/plugins/data_views/public/mocks.ts index e9d5b487b8b006..47b1eb8f8d2e39 100644 --- a/src/plugins/data_views/public/mocks.ts +++ b/src/plugins/data_views/public/mocks.ts @@ -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; }; diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index d05226a0098ce6..55fe8825477d9f 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -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, }, diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts index 1290b607dbc97e..7424e49099105d 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/actions.ts @@ -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, diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/data_view_management/data_view_management.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/data_view_management/data_view_management.tsx index c8d97faece701a..8b3b677f31506d 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/data_view_management/data_view_management.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/data_view_management/data_view_management.tsx @@ -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, }, diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index da083413ea92f4..545a31032155a9 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -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, }, diff --git a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx index 5d7e928c955941..7271fb8e1a80b1 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/datapanel.tsx @@ -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, }, diff --git a/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.tsx b/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.tsx index 47223daee7c994..59bfcc9ec98eb5 100644 --- a/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.tsx +++ b/x-pack/plugins/security_solution/public/sourcerer/experimental/components/dataview_picker/index.tsx @@ -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, }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx index 4e0eefa7703fb0..a5a22fd70f5638 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.test.tsx @@ -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(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.tsx index 6757cd88bd7e7e..435168ba5fbafa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/index.tsx @@ -81,9 +81,9 @@ export const useFieldBrowserOptions: UseFieldBrowserOptions = ({ }, [selectedDataViewId, missingPatterns, dataViews]); const openFieldEditor = useCallback( - (fieldName) => { + async (fieldName) => { if (dataView && selectedDataViewId) { - const closeFieldEditor = dataViewFieldEditor.openEditor({ + const closeFieldEditor = await dataViewFieldEditor.openEditor({ ctx: { dataView }, fieldName, onSave: async (savedFields: DataViewField[]) => {