From 94a1e59319b589cf8732363dc0320bfd55e80db4 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Fri, 14 May 2021 15:41:37 +0200 Subject: [PATCH] [Lens] Remove separate mounting point for editor frame to use redux freely (#99892) remove separate mounting point for editor frame --- .../lens/public/app_plugin/app.test.tsx | 146 ++++++++-------- x-pack/plugins/lens/public/app_plugin/app.tsx | 160 +++++++++--------- .../lens/public/app_plugin/mounter.tsx | 1 - .../plugins/lens/public/app_plugin/types.ts | 4 - .../editor_frame_service/service.test.tsx | 89 ---------- .../public/editor_frame_service/service.tsx | 60 +++---- x-pack/plugins/lens/public/types.ts | 3 +- 7 files changed, 168 insertions(+), 295 deletions(-) delete mode 100644 x-pack/plugins/lens/public/editor_frame_service/service.test.tsx diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 87000865850e12..72b8bfa38491ac 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -45,7 +45,6 @@ import { import { LensAttributeService } from '../lens_attribute_service'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { EmbeddableStateTransfer } from '../../../../../src/plugins/embeddable/public'; -import { NativeRenderer } from '../native_renderer'; import moment from 'moment'; jest.mock('../editor_frame_service/editor_frame/expression_helpers'); @@ -72,8 +71,7 @@ const { TopNavMenu } = navigationStartMock.ui; function createMockFrame(): jest.Mocked { return { - mount: jest.fn(async (el, props) => {}), - unmount: jest.fn(() => {}), + EditorFrameContainer: jest.fn((props: EditorFrameProps) =>
), }; } @@ -308,13 +306,9 @@ describe('Lens App', () => { it('renders the editor frame', () => { const { frame } = mountWith({}); - - expect(frame.mount.mock.calls).toMatchInlineSnapshot(` + expect(frame.EditorFrameContainer.mock.calls).toMatchInlineSnapshot(` Array [ Array [ -
, Object { "dateRange": Object { "fromDate": "2021-01-10T04:00:00.000Z", @@ -333,6 +327,7 @@ describe('Lens App', () => { "searchSessionId": "sessionId-1", "showNoDataPopover": [Function], }, + Object {}, ], ] `); @@ -357,21 +352,20 @@ describe('Lens App', () => { const { component, frame } = mountWith({ services }); component.update(); - - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ dateRange: { fromDate: '2021-01-10T04:00:00.000Z', toDate: '2021-01-10T08:00:00.000Z' }, query: { query: '', language: 'kuery' }, filters: [pinnedFilter], - }) + }), + {} ); expect(services.data.query.filterManager.getFilters).not.toHaveBeenCalled(); }); it('displays errors from the frame in a toast', () => { const { component, frame, services } = mountWith({}); - const onError = frame.mount.mock.calls[0][1].onError; + const onError = frame.EditorFrameContainer.mock.calls[0][0].onError; onError({ message: 'error' }); component.update(); expect(services.notifications.toasts.addDanger).toHaveBeenCalled(); @@ -485,8 +479,7 @@ describe('Lens App', () => { }), {} ); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ doc: expect.objectContaining({ savedObjectId: defaultSavedObjectId, @@ -495,7 +488,8 @@ describe('Lens App', () => { filters: [{ query: { match_phrase: { src: 'test' } } }], }), }), - }) + }), + {} ); }); @@ -619,7 +613,7 @@ describe('Lens App', () => { expect(services.attributeService.unwrapAttributes).not.toHaveBeenCalled(); } - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; act(() => onChange({ @@ -647,7 +641,7 @@ describe('Lens App', () => { }; const { component, frame } = mountWith({ services }); expect(getButton(component).disableButton).toEqual(true); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; act(() => onChange({ filterableIndexPatterns: [], @@ -662,7 +656,7 @@ describe('Lens App', () => { it('shows a save button that is enabled when the frame has provided its state and does not show save and return or save as', async () => { const { component, frame } = mountWith({}); expect(getButton(component).disableButton).toEqual(true); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; act(() => onChange({ filterableIndexPatterns: [], @@ -828,7 +822,7 @@ describe('Lens App', () => { .fn() .mockRejectedValue({ message: 'failed' }); const { component, props, frame } = mountWith({ services }); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; act(() => onChange({ filterableIndexPatterns: [], @@ -906,7 +900,7 @@ describe('Lens App', () => { .fn() .mockReturnValue(Promise.resolve({ savedObjectId: '123' })); const { component, frame } = mountWith({ services }); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; await act(async () => onChange({ filterableIndexPatterns: [], @@ -940,7 +934,7 @@ describe('Lens App', () => { it('does not show the copy button on first save', async () => { const { component, frame } = mountWith({}); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; await act(async () => onChange({ filterableIndexPatterns: [], @@ -967,7 +961,7 @@ describe('Lens App', () => { it('should be disabled when no data is available', async () => { const { component, frame } = mountWith({}); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; await act(async () => onChange({ filterableIndexPatterns: [], @@ -981,7 +975,7 @@ describe('Lens App', () => { it('should disable download when not saveable', async () => { const { component, frame } = mountWith({}); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; await act(async () => onChange({ @@ -1007,7 +1001,7 @@ describe('Lens App', () => { }; const { component, frame } = mountWith({ services }); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; await act(async () => onChange({ filterableIndexPatterns: [], @@ -1032,12 +1026,12 @@ describe('Lens App', () => { }), {} ); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ dateRange: { fromDate: '2021-01-10T04:00:00.000Z', toDate: '2021-01-10T08:00:00.000Z' }, query: { query: '', language: 'kuery' }, - }) + }), + {} ); }); @@ -1049,7 +1043,7 @@ describe('Lens App', () => { }), {} ); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; await act(async () => { onChange({ filterableIndexPatterns: ['1'], @@ -1106,12 +1100,12 @@ describe('Lens App', () => { from: 'now-14d', to: 'now-7d', }); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ dateRange: { fromDate: '2021-01-09T04:00:00.000Z', toDate: '2021-01-09T08:00:00.000Z' }, query: { query: 'new', language: 'lucene' }, - }) + }), + {} ); }); @@ -1125,11 +1119,11 @@ describe('Lens App', () => { ]) ); component.update(); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ filters: [esFilters.buildExistsFilter(field, indexPattern)], - }) + }), + {} ); }); @@ -1142,11 +1136,11 @@ describe('Lens App', () => { }) ); component.update(); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ searchSessionId: `sessionId-1`, - }) + }), + {} ); // trigger again, this time changing just the query @@ -1157,11 +1151,11 @@ describe('Lens App', () => { }) ); component.update(); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ searchSessionId: `sessionId-2`, - }) + }), + {} ); const indexPattern = ({ id: 'index1' } as unknown) as IIndexPattern; @@ -1172,11 +1166,11 @@ describe('Lens App', () => { ]) ); component.update(); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ searchSessionId: `sessionId-3`, - }) + }), + {} ); }); }); @@ -1310,11 +1304,11 @@ describe('Lens App', () => { component.update(); act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!()); component.update(); - expect(frame.mount).toHaveBeenLastCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenLastCalledWith( expect.objectContaining({ filters: [pinned], - }) + }), + {} ); }); }); @@ -1343,11 +1337,11 @@ describe('Lens App', () => { }); }); component.update(); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ searchSessionId: `sessionId-2`, - }) + }), + {} ); }); @@ -1361,11 +1355,11 @@ describe('Lens App', () => { await act(async () => { await new Promise((r) => setTimeout(r, 0)); }); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ searchSessionId: `new-session-id`, - }) + }), + {} ); }); @@ -1387,11 +1381,11 @@ describe('Lens App', () => { component.update(); act(() => component.find(TopNavMenu).prop('onClearSavedQuery')!()); component.update(); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ searchSessionId: `sessionId-2`, - }) + }), + {} ); }); @@ -1416,16 +1410,14 @@ describe('Lens App', () => { it('does not update the searchSessionId when the state changes', () => { const { component, frame } = mountWith({}); act(() => { - (component.find(NativeRenderer).prop('nativeProps') as EditorFrameProps).onChange( - mockUpdate - ); + component.find(frame.EditorFrameContainer).prop('onChange')(mockUpdate); }); component.update(); - expect(frame.mount).not.toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).not.toHaveBeenCalledWith( expect.objectContaining({ searchSessionId: `sessionId-2`, - }) + }), + {} ); }); @@ -1444,16 +1436,14 @@ describe('Lens App', () => { }); act(() => { - (component.find(NativeRenderer).prop('nativeProps') as EditorFrameProps).onChange( - mockUpdate - ); + component.find(frame.EditorFrameContainer).prop('onChange')(mockUpdate); }); component.update(); - expect(frame.mount).toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).toHaveBeenCalledWith( expect.objectContaining({ searchSessionId: `sessionId-2`, - }) + }), + {} ); }); @@ -1472,16 +1462,14 @@ describe('Lens App', () => { }); act(() => { - (component.find(NativeRenderer).prop('nativeProps') as EditorFrameProps).onChange( - mockUpdate - ); + component.find(frame.EditorFrameContainer).prop('onChange')(mockUpdate); }); component.update(); - expect(frame.mount).not.toHaveBeenCalledWith( - expect.any(Element), + expect(frame.EditorFrameContainer).not.toHaveBeenCalledWith( expect.objectContaining({ searchSessionId: `sessionId-2`, - }) + }), + {} ); }); }); @@ -1513,7 +1501,7 @@ describe('Lens App', () => { }, }; const { component, frame, props } = mountWith({ services }); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; act(() => onChange({ filterableIndexPatterns: [], @@ -1533,7 +1521,7 @@ describe('Lens App', () => { it('should confirm when leaving with an unsaved doc', () => { const { component, frame, props } = mountWith({}); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; act(() => onChange({ filterableIndexPatterns: [], @@ -1553,7 +1541,7 @@ describe('Lens App', () => { await act(async () => { component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; act(() => onChange({ filterableIndexPatterns: [], @@ -1576,7 +1564,7 @@ describe('Lens App', () => { await act(async () => { component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; act(() => onChange({ filterableIndexPatterns: [], @@ -1596,7 +1584,7 @@ describe('Lens App', () => { await act(async () => { component.setProps({ initialInput: { savedObjectId: defaultSavedObjectId } }); }); - const onChange = frame.mount.mock.calls[0][1].onChange; + const onChange = frame.EditorFrameContainer.mock.calls[0][0].onChange; act(() => onChange({ filterableIndexPatterns: [], diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 077456423ac4d4..c172f36913c217 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -26,7 +26,6 @@ import { checkForDuplicateTitle, } from '../../../../../src/plugins/saved_objects/public'; import { injectFilterReferences } from '../persistence'; -import { NativeRenderer } from '../native_renderer'; import { trackUiEvent } from '../lens_ui_telemetry'; import { DataPublicPluginStart, @@ -82,7 +81,7 @@ export function App({ dashboardFeatureFlag, } = useKibana().services; - const startSession = useCallback(() => data.search.session.start(), [data]); + const startSession = useCallback(() => data.search.session.start(), [data.search.session]); const [state, setState] = useState(() => { return { @@ -95,26 +94,28 @@ export function App({ isLoading: Boolean(initialInput), indexPatternsForTopNav: [], isLinkedToOriginatingApp: Boolean(incomingState?.originatingApp), - isSaveModalVisible: false, - indicateNoData: false, isSaveable: false, searchSessionId: startSession(), }; }); + // Used to show a popover that guides the user towards changing the date range when no data is available. + const [indicateNoData, setIndicateNoData] = useState(false); + const [isSaveModalVisible, setIsSaveModalVisible] = useState(false); + const { lastKnownDoc } = state; const showNoDataPopover = useCallback(() => { - setState((prevState) => ({ ...prevState, indicateNoData: true })); - }, [setState]); + setIndicateNoData(true); + }, [setIndicateNoData]); useEffect(() => { - if (state.indicateNoData) { - setState((prevState) => ({ ...prevState, indicateNoData: false })); + if (indicateNoData) { + setIndicateNoData(false); } }, [ - setState, - state.indicateNoData, + setIndicateNoData, + indicateNoData, state.query, state.filters, state.indexPatternsForTopNav, @@ -136,26 +137,6 @@ export function App({ [notifications.toasts] ); - const getLastKnownDocWithoutPinnedFilters = useCallback( - function () { - if (!lastKnownDoc) return undefined; - const [pinnedFilters, appFilters] = _.partition( - injectFilterReferences(lastKnownDoc.state?.filters || [], lastKnownDoc.references), - esFilters.isFilterPinned - ); - return pinnedFilters?.length - ? { - ...lastKnownDoc, - state: { - ...lastKnownDoc.state, - filters: appFilters, - }, - } - : lastKnownDoc; - }, - [lastKnownDoc] - ); - const getIsByValueMode = useCallback( () => Boolean( @@ -263,7 +244,10 @@ export function App({ // or when the user has configured something without saving if ( application.capabilities.visualize.save && - !_.isEqual(state.persistedDoc?.state, getLastKnownDocWithoutPinnedFilters()?.state) && + !_.isEqual( + state.persistedDoc?.state, + getLastKnownDocWithoutPinnedFilters(lastKnownDoc)?.state + ) && (state.isSaveable || state.persistedDoc) ) { return actions.confirm( @@ -283,7 +267,6 @@ export function App({ lastKnownDoc, state.isSaveable, state.persistedDoc, - getLastKnownDocWithoutPinnedFilters, application.capabilities.visualize.save, ]); @@ -374,7 +357,7 @@ export function App({ setState((s) => ({ ...s, isLoading: false, - persistedDoc: doc, + ...(!_.isEqual(state.persistedDoc, doc) ? { persistedDoc: doc } : null), lastKnownDoc: doc, query: doc.state.query, indexPatternsForTopNav: indexPatterns, @@ -403,8 +386,7 @@ export function App({ attributeService, redirectTo, chrome.recentlyAccessed, - state.persistedDoc?.savedObjectId, - state.persistedDoc?.state, + state.persistedDoc, ]); const tagsIds = @@ -435,7 +417,7 @@ export function App({ } const docToSave = { - ...getLastKnownDocWithoutPinnedFilters()!, + ...getLastKnownDocWithoutPinnedFilters(lastKnownDoc)!, description: saveProps.newDescription, title: saveProps.newTitle, references, @@ -522,9 +504,10 @@ export function App({ ); setState((s) => ({ ...s, - isSaveModalVisible: false, isLinkedToOriginatingApp: false, })); + + setIsSaveModalVisible(false); // remove editor state so the connection is still broken after reload stateTransfer.clearEditorState(APP_ID); @@ -540,14 +523,15 @@ export function App({ ...s, persistedDoc: newDoc, lastKnownDoc: newDoc, - isSaveModalVisible: false, isLinkedToOriginatingApp: false, })); + + setIsSaveModalVisible(false); } catch (e) { // eslint-disable-next-line no-console console.dir(e); trackUiEvent('save_failed'); - setState((s) => ({ ...s, isSaveModalVisible: false })); + setIsSaveModalVisible(false); } }; @@ -634,7 +618,7 @@ export function App({ }, showSaveModal: () => { if (savingToDashboardPermitted || savingToLibraryPermitted) { - setState((s) => ({ ...s, isSaveModalVisible: true })); + setIsSaveModalVisible(true); } }, cancel: () => { @@ -706,7 +690,7 @@ export function App({ query={state.query} dateRangeFrom={fromDate} dateRangeTo={toDate} - indicateNoData={state.indicateNoData} + indicateNoData={indicateNoData} /> {(!state.isLoading || state.persistedDoc) && ( { - setState((s) => ({ ...s, isSaveModalVisible: false })); + setIsSaveModalVisible(false); }} getAppNameFromId={() => getOriginatingAppName()} lastKnownDoc={lastKnownDoc} @@ -790,47 +774,44 @@ const MemoizedEditorFrameWrapper = React.memo(function EditorFrameWrapper({ lastKnownDoc: React.MutableRefObject; activeData: React.MutableRefObject | undefined>; }) { + const { EditorFrameContainer } = editorFrame; return ( - { - if (isSaveable !== oldIsSaveable) { - setState((s) => ({ ...s, isSaveable })); - } - if (!_.isEqual(persistedDoc, doc) && !_.isEqual(lastKnownDoc.current, doc)) { - setState((s) => ({ ...s, lastKnownDoc: doc })); - } - if (!_.isEqual(activeDataRef.current, activeData)) { - setState((s) => ({ ...s, activeData })); - } + { + if (isSaveable !== oldIsSaveable) { + setState((s) => ({ ...s, isSaveable })); + } + if (!_.isEqual(persistedDoc, doc) && !_.isEqual(lastKnownDoc.current, doc)) { + setState((s) => ({ ...s, lastKnownDoc: doc })); + } + if (!_.isEqual(activeDataRef.current, activeData)) { + setState((s) => ({ ...s, activeData })); + } - // Update the cached index patterns if the user made a change to any of them - if ( - indexPatternsForTopNav.length !== filterableIndexPatterns.length || - filterableIndexPatterns.some( - (id) => !indexPatternsForTopNav.find((indexPattern) => indexPattern.id === id) - ) - ) { - getAllIndexPatterns(filterableIndexPatterns, data.indexPatterns).then( - ({ indexPatterns }) => { - if (indexPatterns) { - setState((s) => ({ ...s, indexPatternsForTopNav: indexPatterns })); - } + // Update the cached index patterns if the user made a change to any of them + if ( + indexPatternsForTopNav.length !== filterableIndexPatterns.length || + filterableIndexPatterns.some( + (id) => !indexPatternsForTopNav.find((indexPattern) => indexPattern.id === id) + ) + ) { + getAllIndexPatterns(filterableIndexPatterns, data.indexPatterns).then( + ({ indexPatterns }) => { + if (indexPatterns) { + setState((s) => ({ ...s, indexPatternsForTopNav: indexPatterns })); } - ); - } - }, + } + ); + } }} /> ); @@ -851,3 +832,20 @@ export async function getAllIndexPatterns( // return also the rejected ids in case we want to show something later on return { indexPatterns: fullfilled.map((response) => response.value), rejectedIds }; } + +function getLastKnownDocWithoutPinnedFilters(doc?: Document) { + if (!doc) return undefined; + const [pinnedFilters, appFilters] = _.partition( + injectFilterReferences(doc.state?.filters || [], doc.references), + esFilters.isFilterPinned + ); + return pinnedFilters?.length + ? { + ...doc, + state: { + ...doc.state, + filters: appFilters, + }, + } + : doc; +} diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 5869151485a526..e6eb115562d378 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -230,7 +230,6 @@ export async function mountApp( ); return () => { data.search.session.clear(); - instance.unmount(); unmountComponentAtNode(params.element); unlistenParentHistory(); }; diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index b96b274c3c1596..c9143542e67bfb 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -45,10 +45,6 @@ export interface LensAppState { isLoading: boolean; persistedDoc?: Document; lastKnownDoc?: Document; - isSaveModalVisible: boolean; - - // Used to show a popover that guides the user towards changing the date range when no data is available. - indicateNoData: boolean; // index patterns used to determine which filters are available in the top nav. indexPatternsForTopNav: IndexPattern[]; diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx deleted file mode 100644 index 9174f4387293a1..00000000000000 --- a/x-pack/plugins/lens/public/editor_frame_service/service.test.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EditorFrameService } from './service'; -import { coreMock } from 'src/core/public/mocks'; -import { - MockedSetupDependencies, - MockedStartDependencies, - createMockSetupDependencies, - createMockStartDependencies, -} from './mocks'; -import { CoreSetup } from 'kibana/public'; - -// mock away actual dependencies to prevent all of it being loaded -jest.mock('./embeddable/embeddable_factory', () => ({ - EmbeddableFactory: class Mock {}, -})); - -describe('editor_frame service', () => { - let pluginInstance: EditorFrameService; - let mountpoint: Element; - let pluginSetupDependencies: MockedSetupDependencies; - let pluginStartDependencies: MockedStartDependencies; - - beforeEach(() => { - pluginInstance = new EditorFrameService(); - mountpoint = document.createElement('div'); - pluginSetupDependencies = createMockSetupDependencies(); - pluginStartDependencies = createMockStartDependencies(); - }); - - afterEach(() => { - mountpoint.remove(); - }); - - it('should create an editor frame instance which mounts and unmounts', async () => { - await expect( - (async () => { - pluginInstance.setup( - coreMock.createSetup() as CoreSetup, - pluginSetupDependencies, - jest.fn() - ); - const publicAPI = pluginInstance.start(coreMock.createStart(), pluginStartDependencies); - const instance = await publicAPI.createInstance(); - instance.mount(mountpoint, { - onError: jest.fn(), - onChange: jest.fn(), - dateRange: { fromDate: '', toDate: '' }, - query: { query: '', language: 'lucene' }, - filters: [], - showNoDataPopover: jest.fn(), - initialContext: { - indexPatternId: '1', - fieldName: 'test', - }, - searchSessionId: 'sessionId', - }); - instance.unmount(); - })() - ).resolves.toBeUndefined(); - }); - - it('should not have child nodes after unmount', async () => { - pluginInstance.setup( - coreMock.createSetup() as CoreSetup, - pluginSetupDependencies, - jest.fn() - ); - const publicAPI = pluginInstance.start(coreMock.createStart(), pluginStartDependencies); - const instance = await publicAPI.createInstance(); - instance.mount(mountpoint, { - onError: jest.fn(), - onChange: jest.fn(), - dateRange: { fromDate: '', toDate: '' }, - query: { query: '', language: 'lucene' }, - filters: [], - showNoDataPopover: jest.fn(), - searchSessionId: 'sessionId', - }); - instance.unmount(); - - expect(mountpoint.hasChildNodes()).toBe(false); - }); -}); diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 46dc326a015a87..f6500596ce5a0e 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -6,8 +6,6 @@ */ import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nProvider } from '@kbn/i18n/react'; import { CoreSetup, CoreStart } from 'kibana/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public'; @@ -123,47 +121,33 @@ export class EditorFrameService { public start(core: CoreStart, plugins: EditorFrameStartPlugins): EditorFrameStart { const createInstance = async (): Promise => { - let domElement: Element; const [resolvedDatasources, resolvedVisualizations] = await Promise.all([ collectAsyncDefinitions(this.datasources), collectAsyncDefinitions(this.visualizations), ]); - const unmount = () => { - if (domElement) { - unmountComponentAtNode(domElement); - } - }; + const firstDatasourceId = Object.keys(resolvedDatasources)[0]; + const firstVisualizationId = Object.keys(resolvedVisualizations)[0]; + + const { EditorFrame, getActiveDatasourceIdFromDoc } = await import('../async_services'); + + const palettes = await plugins.charts.palettes.getPalettes(); return { - mount: async ( - element, - { - doc, - onError, - dateRange, - query, - filters, - savedQuery, - onChange, - showNoDataPopover, - initialContext, - searchSessionId, - } - ) => { - if (domElement !== element) { - unmount(); - } - domElement = element; - const firstDatasourceId = Object.keys(resolvedDatasources)[0]; - const firstVisualizationId = Object.keys(resolvedVisualizations)[0]; - - const { EditorFrame, getActiveDatasourceIdFromDoc } = await import('../async_services'); - - const palettes = await plugins.charts.palettes.getPalettes(); - - render( - + EditorFrameContainer: ({ + doc, + onError, + dateRange, + query, + filters, + savedQuery, + onChange, + showNoDataPopover, + initialContext, + searchSessionId, + }) => { + return ( +
- , - domElement +
); }, - unmount, }; }; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 51d679e7c40e5d..9cde4eb8a15616 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -65,8 +65,7 @@ export interface EditorFrameProps { showNoDataPopover: () => void; } export interface EditorFrameInstance { - mount: (element: Element, props: EditorFrameProps) => Promise; - unmount: () => void; + EditorFrameContainer: (props: EditorFrameProps) => React.ReactElement; } export interface EditorFrameSetup {