diff --git a/src/plugins/data_explorer/public/components/app.tsx b/src/plugins/data_explorer/public/components/app.tsx index 55544844b22..8a36443da43 100644 --- a/src/plugins/data_explorer/public/components/app.tsx +++ b/src/plugins/data_explorer/public/components/app.tsx @@ -4,12 +4,9 @@ */ import React from 'react'; -import { EuiPageTemplate } from '@elastic/eui'; - import { CoreStart, ScopedHistory } from '../../../../core/public'; -import { Sidebar } from './sidebar'; import { useView } from '../utils/use'; -import { NoView } from './no_view'; +import { AppContainer } from './app_container'; interface DataExplorerAppDeps { basename: string; @@ -18,24 +15,8 @@ interface DataExplorerAppDeps { history: ScopedHistory; } -export const DataExplorerApp = ({ basename, history }: DataExplorerAppDeps) => { +export const DataExplorerApp = (deps: DataExplorerAppDeps) => { const { view } = useView(); - if (!view) { - return ; - } - - // Render the application DOM. - // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract. - return ( - } - className="dePageTemplate" - template="default" - restrictWidth={false} - paddingSize="none" - > - {view.ui.canvas} - - ); + return ; }; diff --git a/src/plugins/data_explorer/public/components/app_container.test.tsx b/src/plugins/data_explorer/public/components/app_container.test.tsx new file mode 100644 index 00000000000..d4215f24e79 --- /dev/null +++ b/src/plugins/data_explorer/public/components/app_container.test.tsx @@ -0,0 +1,47 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { AppContainer } from './app_container'; +import { render } from '@testing-library/react'; +import { View } from '../services/view_service/view'; +import { ViewMountParameters } from '../services/view_service'; + +describe('DataExplorerApp', () => { + const createView = () => { + return new View({ + id: 'test-view', + title: 'Test View', + defaultPath: '/test-path', + appExtentions: {} as any, + mount: async ({ canvasElement, panelElement }: ViewMountParameters) => { + const canvasContent = document.createElement('div'); + const panelContent = document.createElement('div'); + canvasContent.innerHTML = 'canvas-content'; + panelContent.innerHTML = 'panel-content'; + canvasElement.appendChild(canvasContent); + panelElement.appendChild(panelContent); + return () => { + canvasContent.remove(); + panelContent.remove(); + }; + }, + }); + }; + + it('should render NoView when a non existent view is selected', () => { + const { container } = render(); + + expect(container).toContainHTML('View not found'); + }); + + // TODO: Complete once state management is in place + // it('should render the canvas and panel when selected', () => { + // const view = createView(); + // const { container } = render(); + + // expect(container).toMatchSnapshot(); + // }); +}); diff --git a/src/plugins/data_explorer/public/components/app_container.tsx b/src/plugins/data_explorer/public/components/app_container.tsx new file mode 100644 index 00000000000..9044a6c69a2 --- /dev/null +++ b/src/plugins/data_explorer/public/components/app_container.tsx @@ -0,0 +1,83 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useLayoutEffect, useRef, useState } from 'react'; +import { EuiPageTemplate } from '@elastic/eui'; + +import { Sidebar } from './sidebar'; +import { NoView } from './no_view'; +import { View } from '../services/view_service/view'; + +export const AppContainer = ({ view }: { view?: View }) => { + const [showSpinner, setShowSpinner] = useState(false); + const canvasRef = useRef(null); + const panelRef = useRef(null); + const unmountRef = useRef(null); + + useLayoutEffect(() => { + const unmount = () => { + if (unmountRef.current) { + unmountRef.current(); + unmountRef.current = null; + } + }; + + if (!view) { + return; + } + + // unmount the previous view + unmount(); + + const mount = async () => { + setShowSpinner(true); + try { + unmountRef.current = + (await view.mount({ + canvasElement: canvasRef.current!, + panelElement: panelRef.current!, + })) || null; + } catch (e) { + // TODO: add error UI + // eslint-disable-next-line no-console + console.error(e); + } finally { + // if (canvasRef.current && panelRef.current) { + if (canvasRef.current) { + setShowSpinner(false); + } + } + }; + + mount(); + + return unmount; + }, [view]); + + // TODO: Make this more robust. + if (!view) { + return ; + } + + // Render the application DOM. + // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract. + return ( + +
+ + } + className="dePageTemplate" + template="default" + restrictWidth={false} + paddingSize="none" + > + {/* TODO: improve loading state */} + {showSpinner &&
Loading...
} +
+ + ); +}; diff --git a/src/plugins/data_explorer/public/components/sidebar.tsx b/src/plugins/data_explorer/public/components/sidebar.tsx index 67ce3bae56d..3d6e5070a5e 100644 --- a/src/plugins/data_explorer/public/components/sidebar.tsx +++ b/src/plugins/data_explorer/public/components/sidebar.tsx @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useMemo } from 'react'; +import React, { useMemo, FC } from 'react'; import { EuiPanel, EuiComboBox, EuiSelect, EuiSelectOption } from '@elastic/eui'; import { useView } from '../utils/use'; -export const Sidebar = () => { +export const Sidebar: FC = ({ children }) => { const { view, viewRegistry } = useView(); const views = viewRegistry.all(); const viewOptions: EuiSelectOption[] = useMemo( @@ -34,7 +34,7 @@ export const Sidebar = () => { /> - {view?.ui.panel} + {children} ); }; diff --git a/src/plugins/data_explorer/public/index.ts b/src/plugins/data_explorer/public/index.ts index c999a605b52..ce419fbdce0 100644 --- a/src/plugins/data_explorer/public/index.ts +++ b/src/plugins/data_explorer/public/index.ts @@ -13,3 +13,4 @@ export function plugin() { return new DataExplorerPlugin(); } export { DataExplorerPluginSetup, DataExplorerPluginStart, ViewRedirectParams } from './types'; +export { ViewMountParameters, ViewDefinition } from './services/view_service'; diff --git a/src/plugins/data_explorer/public/services/view_service/types.ts b/src/plugins/data_explorer/public/services/view_service/types.ts index 03d5c6bf47b..ee13e8a6b46 100644 --- a/src/plugins/data_explorer/public/services/view_service/types.ts +++ b/src/plugins/data_explorer/public/services/view_service/types.ts @@ -2,7 +2,6 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ -import { ReactElement } from 'react'; // TODO: Correctly type this file. @@ -11,15 +10,19 @@ interface ViewListItem { label: string; } +export interface ViewMountParameters { + canvasElement: HTMLDivElement; + panelElement: HTMLDivElement; +} + export interface ViewDefinition { readonly id: string; readonly title: string; - readonly ui: { - panel: ReactElement; - canvas: ReactElement; + readonly ui?: { defaults: T; reducer: (state: T, action: any) => T; }; + readonly mount: (params: ViewMountParameters) => Promise<() => void>; readonly defaultPath: string; readonly appExtentions: { savedObject: { diff --git a/src/plugins/data_explorer/public/services/view_service/view.ts b/src/plugins/data_explorer/public/services/view_service/view.ts index 5abd2676102..1e994a21b03 100644 --- a/src/plugins/data_explorer/public/services/view_service/view.ts +++ b/src/plugins/data_explorer/public/services/view_service/view.ts @@ -13,6 +13,7 @@ export class View implements IView { public readonly defaultPath: string; public readonly appExtentions: IView['appExtentions']; readonly shouldShow?: (state: any) => boolean; + readonly mount: IView['mount']; constructor(options: ViewDefinition) { this.id = options.id; @@ -21,5 +22,6 @@ export class View implements IView { this.defaultPath = options.defaultPath; this.appExtentions = options.appExtentions; this.shouldShow = options.shouldShow; + this.mount = options.mount; } } diff --git a/src/plugins/discover/public/application/view_components/canvas/create_canvas.tsx b/src/plugins/discover/public/application/view_components/canvas/create_canvas.tsx deleted file mode 100644 index b223b336e44..00000000000 --- a/src/plugins/discover/public/application/view_components/canvas/create_canvas.tsx +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; - -export const createCanvas = () => { - return
Test Canvas
; -}; diff --git a/src/plugins/discover/public/application/view_components/canvas/index.tsx b/src/plugins/discover/public/application/view_components/canvas/index.tsx new file mode 100644 index 00000000000..e7b894b18a9 --- /dev/null +++ b/src/plugins/discover/public/application/view_components/canvas/index.tsx @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { ViewMountParameters } from '../../../../../data_explorer/public'; +import { OpenSearchDashboardsContextProvider } from '../../../../../opensearch_dashboards_react/public'; +import { DiscoverServices } from '../../../build_services'; + +export const renderCanvas = ( + { canvasElement }: ViewMountParameters, + services: DiscoverServices +) => { + ReactDOM.render( + + {/* This is dummy code, inline styles will not be added in production */} +
+ {JSON.stringify(services.capabilities.navLinks, null, 2)} +
+
, + canvasElement + ); + + return () => ReactDOM.unmountComponentAtNode(canvasElement); +}; diff --git a/src/plugins/discover/public/application/view_components/canvas/index.ts b/src/plugins/discover/public/application/view_components/index.ts similarity index 62% rename from src/plugins/discover/public/application/view_components/canvas/index.ts rename to src/plugins/discover/public/application/view_components/index.ts index 4c418d41a67..45fd68cf128 100644 --- a/src/plugins/discover/public/application/view_components/canvas/index.ts +++ b/src/plugins/discover/public/application/view_components/index.ts @@ -3,4 +3,5 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from './create_canvas'; +export * from './canvas'; +export * from './panel'; diff --git a/src/plugins/discover/public/application/view_components/panel/create_panel.tsx b/src/plugins/discover/public/application/view_components/panel/create_panel.tsx deleted file mode 100644 index 15735fe81d8..00000000000 --- a/src/plugins/discover/public/application/view_components/panel/create_panel.tsx +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; - -export const createPanel = () => { - return
Test Panel
; -}; diff --git a/src/plugins/discover/public/application/view_components/panel/index.ts b/src/plugins/discover/public/application/view_components/panel/index.ts deleted file mode 100644 index 8a992cb4994..00000000000 --- a/src/plugins/discover/public/application/view_components/panel/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export * from './create_panel'; diff --git a/src/plugins/discover/public/application/view_components/panel/index.tsx b/src/plugins/discover/public/application/view_components/panel/index.tsx new file mode 100644 index 00000000000..9e4d9a040e2 --- /dev/null +++ b/src/plugins/discover/public/application/view_components/panel/index.tsx @@ -0,0 +1,21 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { ViewMountParameters } from '../../../../../data_explorer/public'; +import { OpenSearchDashboardsContextProvider } from '../../../../../opensearch_dashboards_react/public'; +import { DiscoverServices } from '../../../build_services'; + +export const renderPanel = ({ panelElement }: ViewMountParameters, services: DiscoverServices) => { + ReactDOM.render( + +
Side panel
+
, + panelElement + ); + + return () => ReactDOM.unmountComponentAtNode(panelElement); +}; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 1070461abeb..34cc84ee26d 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -89,8 +89,6 @@ import { import { DISCOVER_LEGACY_TOGGLE, PLUGIN_ID } from '../common'; import { DataExplorerPluginSetup, ViewRedirectParams } from '../../data_explorer/public'; import { registerFeature } from './register_feature'; -import { createCanvas } from './application/view_components/canvas'; -import { createPanel } from './application/view_components/panel'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -105,7 +103,6 @@ export interface DiscoverSetup { docViews: { /** * Add new doc view shown along with table view and json view in the details of each document in Discover. - * Both react and angular doc views are supported. * @param docViewRaw */ addDocView(docViewRaw: DocViewInput | DocViewInputFn): void; @@ -389,12 +386,20 @@ export class DiscoverPlugin }, }, ui: { - canvas: createCanvas(), - panel: createPanel(), defaults: {}, reducer: () => ({}), }, shouldShow: () => true, + mount: async (params) => { + const { renderCanvas, renderPanel } = await import('./application/view_components'); + const [coreStart, pluginsStart] = await core.getStartServices(); + const services = await buildServices(coreStart, pluginsStart, this.initializerContext); + + renderCanvas(params, services); + renderPanel(params, services); + + return () => {}; + }, }); // this.registerEmbeddable(core, plugins);