From 6e0a855dec1a84f3b7fc275f7fe8c2c9873ca2f8 Mon Sep 17 00:00:00 2001 From: Ashwin P Chandran Date: Tue, 18 Jul 2023 09:33:52 -0700 Subject: [PATCH] [Data Explorer] Adds toggle between legacy and new discover (#4563) * Adds toggle between legacy and new discover Signed-off-by: Ashwin P Chandran * Fixes header offset Signed-off-by: Ashwin P Chandran --------- Signed-off-by: Ashwin P Chandran --- .../data_explorer/opensearch_dashboards.json | 2 +- .../data_explorer/public/application.tsx | 10 +-- .../data_explorer/public/components/app.tsx | 13 +-- .../public/components/app_container.tsx | 10 ++- src/plugins/data_explorer/public/index.scss | 4 + src/plugins/data_explorer/public/plugin.ts | 25 +++++- .../public/services/view_service/types.ts | 5 +- src/plugins/data_explorer/public/types.ts | 7 ++ src/plugins/discover/common/index.ts | 2 +- .../view_components/canvas/canvas.tsx | 27 +++++++ .../view_components/canvas/index.tsx | 14 ++-- .../view_components/canvas/top_nav.tsx | 79 +++++++++++++++++++ .../view_components/panel/index.tsx | 3 +- .../view_components/panel/panel.tsx | 10 +++ src/plugins/discover/public/index.ts | 1 + src/plugins/discover/public/plugin.ts | 7 +- src/plugins/discover/server/ui_settings.ts | 6 +- .../opensearch_dashboards.json | 14 +++- .../public/application/angular/discover.js | 17 ++++ src/plugins/discover_legacy/public/plugin.ts | 21 +++++ 20 files changed, 234 insertions(+), 43 deletions(-) create mode 100644 src/plugins/discover/public/application/view_components/canvas/canvas.tsx create mode 100644 src/plugins/discover/public/application/view_components/canvas/top_nav.tsx create mode 100644 src/plugins/discover/public/application/view_components/panel/panel.tsx diff --git a/src/plugins/data_explorer/opensearch_dashboards.json b/src/plugins/data_explorer/opensearch_dashboards.json index 7bf93a9c24d..7b5a78c5503 100644 --- a/src/plugins/data_explorer/opensearch_dashboards.json +++ b/src/plugins/data_explorer/opensearch_dashboards.json @@ -4,7 +4,7 @@ "opensearchDashboardsVersion": "opensearchDashboards", "server": true, "ui": true, - "requiredPlugins": ["navigation"], + "requiredPlugins": ["data", "navigation"], "optionalPlugins": [], "requiredBundles": ["opensearchDashboardsReact"] } diff --git a/src/plugins/data_explorer/public/application.tsx b/src/plugins/data_explorer/public/application.tsx index 7491ae7d867..505209358ff 100644 --- a/src/plugins/data_explorer/public/application.tsx +++ b/src/plugins/data_explorer/public/application.tsx @@ -14,20 +14,16 @@ import { DataExplorerApp } from './components/app'; export const renderApp = ( { notifications, http }: CoreStart, services: DataExplorerServices, - { appBasePath, element, history }: AppMountParameters + params: AppMountParameters ) => { + const { history, element } = params; ReactDOM.render( - + diff --git a/src/plugins/data_explorer/public/components/app.tsx b/src/plugins/data_explorer/public/components/app.tsx index 8a36443da43..40d23d97356 100644 --- a/src/plugins/data_explorer/public/components/app.tsx +++ b/src/plugins/data_explorer/public/components/app.tsx @@ -4,19 +4,12 @@ */ import React from 'react'; -import { CoreStart, ScopedHistory } from '../../../../core/public'; +import { AppMountParameters } from '../../../../core/public'; import { useView } from '../utils/use'; import { AppContainer } from './app_container'; -interface DataExplorerAppDeps { - basename: string; - notifications: CoreStart['notifications']; - http: CoreStart['http']; - history: ScopedHistory; -} - -export const DataExplorerApp = (deps: DataExplorerAppDeps) => { +export const DataExplorerApp = ({ params }: { params: AppMountParameters }) => { const { view } = useView(); - return ; + return ; }; diff --git a/src/plugins/data_explorer/public/components/app_container.tsx b/src/plugins/data_explorer/public/components/app_container.tsx index 9044a6c69a2..8923d2c5b17 100644 --- a/src/plugins/data_explorer/public/components/app_container.tsx +++ b/src/plugins/data_explorer/public/components/app_container.tsx @@ -5,12 +5,12 @@ import React, { useLayoutEffect, useRef, useState } from 'react'; import { EuiPageTemplate } from '@elastic/eui'; - +import { AppMountParameters } from '../../../../core/public'; import { Sidebar } from './sidebar'; import { NoView } from './no_view'; import { View } from '../services/view_service/view'; -export const AppContainer = ({ view }: { view?: View }) => { +export const AppContainer = ({ view, params }: { view?: View; params: AppMountParameters }) => { const [showSpinner, setShowSpinner] = useState(false); const canvasRef = useRef(null); const panelRef = useRef(null); @@ -24,7 +24,8 @@ export const AppContainer = ({ view }: { view?: View }) => { } }; - if (!view) { + // Do nothing if the view is not defined or if the view is the same as the previous view + if (!view || (unmountRef.current && unmountRef.current.viewId === view.id)) { return; } @@ -38,6 +39,7 @@ export const AppContainer = ({ view }: { view?: View }) => { (await view.mount({ canvasElement: canvasRef.current!, panelElement: panelRef.current!, + appParams: params, })) || null; } catch (e) { // TODO: add error UI @@ -54,7 +56,7 @@ export const AppContainer = ({ view }: { view?: View }) => { mount(); return unmount; - }, [view]); + }, [params, view]); // TODO: Make this more robust. if (!view) { diff --git a/src/plugins/data_explorer/public/index.scss b/src/plugins/data_explorer/public/index.scss index 1a807b7b60a..8389e31b426 100644 --- a/src/plugins/data_explorer/public/index.scss +++ b/src/plugins/data_explorer/public/index.scss @@ -3,3 +3,7 @@ $osdHeaderOffset: $euiHeaderHeightCompensation; .dePageTemplate { height: calc(100vh - #{$osdHeaderOffset}); } + +.headerIsExpanded .dePageTemplate { + height: calc(100vh - #{$osdHeaderOffset * 2}); +} diff --git a/src/plugins/data_explorer/public/plugin.ts b/src/plugins/data_explorer/public/plugin.ts index 3075cd27e83..d44e6713cc7 100644 --- a/src/plugins/data_explorer/public/plugin.ts +++ b/src/plugins/data_explorer/public/plugin.ts @@ -10,15 +10,29 @@ import { Plugin, AppNavLinkStatus, } from '../../../core/public'; -import { DataExplorerPluginSetup, DataExplorerPluginStart, DataExplorerServices } from './types'; +import { + DataExplorerPluginSetup, + DataExplorerPluginSetupDependencies, + DataExplorerPluginStart, + DataExplorerPluginStartDependencies, + DataExplorerServices, +} from './types'; import { PLUGIN_ID, PLUGIN_NAME } from '../common'; import { ViewService } from './services/view_service'; export class DataExplorerPlugin - implements Plugin { + implements + Plugin< + DataExplorerPluginSetup, + DataExplorerPluginStart, + DataExplorerPluginSetupDependencies, + DataExplorerPluginStartDependencies + > { private viewService = new ViewService(); - public setup(core: CoreSetup): DataExplorerPluginSetup { + public setup( + core: CoreSetup + ): DataExplorerPluginSetup { const viewService = this.viewService; // Register an application into the side navigation menu core.application.register({ @@ -26,7 +40,10 @@ export class DataExplorerPlugin title: PLUGIN_NAME, navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { - const [coreStart, depsStart] = await core.getStartServices(); + const [coreStart, pluginsStart] = await core.getStartServices(); + + // make sure the index pattern list is up to date + pluginsStart.data.indexPatterns.clearCache(); const services: DataExplorerServices = { ...coreStart, 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 ee13e8a6b46..d56f8f3058b 100644 --- a/src/plugins/data_explorer/public/services/view_service/types.ts +++ b/src/plugins/data_explorer/public/services/view_service/types.ts @@ -3,7 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -// TODO: Correctly type this file. +import { AppMountParameters } from '../../../../../core/public'; + +// TODO: State management props interface ViewListItem { id: string; @@ -13,6 +15,7 @@ interface ViewListItem { export interface ViewMountParameters { canvasElement: HTMLDivElement; panelElement: HTMLDivElement; + appParams: AppMountParameters; } export interface ViewDefinition { diff --git a/src/plugins/data_explorer/public/types.ts b/src/plugins/data_explorer/public/types.ts index 00c61864fe1..8976745eb3f 100644 --- a/src/plugins/data_explorer/public/types.ts +++ b/src/plugins/data_explorer/public/types.ts @@ -5,6 +5,7 @@ import { CoreStart } from 'opensearch-dashboards/public'; import { ViewService } from './services/view_service'; +import { DataPublicPluginStart } from '../../data/public'; export interface DataExplorerPluginSetup { registerView: ViewService['registerView']; @@ -12,6 +13,12 @@ export interface DataExplorerPluginSetup { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface DataExplorerPluginStart {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DataExplorerPluginSetupDependencies {} +export interface DataExplorerPluginStartDependencies { + data: DataPublicPluginStart; +} + export interface ViewRedirectParams { view: string; path?: string; diff --git a/src/plugins/discover/common/index.ts b/src/plugins/discover/common/index.ts index d41669c1695..0cac73333e2 100644 --- a/src/plugins/discover/common/index.ts +++ b/src/plugins/discover/common/index.ts @@ -4,7 +4,7 @@ */ export const PLUGIN_ID = 'discover'; -export const DISCOVER_LEGACY_TOGGLE = 'discover:legacyToggle'; +export const NEW_DISCOVER_APP = 'discover:v2'; export const DEFAULT_COLUMNS_SETTING = 'defaultColumns'; export const SAMPLE_SIZE_SETTING = 'discover:sampleSize'; export const AGGS_TERMS_SIZE_SETTING = 'discover:aggs:terms:size'; diff --git a/src/plugins/discover/public/application/view_components/canvas/canvas.tsx b/src/plugins/discover/public/application/view_components/canvas/canvas.tsx new file mode 100644 index 00000000000..8f512a10837 --- /dev/null +++ b/src/plugins/discover/public/application/view_components/canvas/canvas.tsx @@ -0,0 +1,27 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { AppMountParameters } from '../../../../../../core/public'; +import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; +import { DiscoverServices } from '../../../build_services'; +import { TopNav } from './top_nav'; + +interface CanvasProps { + opts: { + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + }; +} + +export const Canvas = ({ opts }: CanvasProps) => { + const { services } = useOpenSearchDashboards(); + + return ( +
+ + 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 index e7b894b18a9..2e911832d14 100644 --- a/src/plugins/discover/public/application/view_components/canvas/index.tsx +++ b/src/plugins/discover/public/application/view_components/canvas/index.tsx @@ -8,17 +8,21 @@ import ReactDOM from 'react-dom'; import { ViewMountParameters } from '../../../../../data_explorer/public'; import { OpenSearchDashboardsContextProvider } from '../../../../../opensearch_dashboards_react/public'; import { DiscoverServices } from '../../../build_services'; +import { Canvas } from './canvas'; export const renderCanvas = ( - { canvasElement }: ViewMountParameters, + { canvasElement, appParams }: ViewMountParameters, services: DiscoverServices ) => { + const { setHeaderActionMenu } = appParams; + ReactDOM.render( - {/* This is dummy code, inline styles will not be added in production */} -
- {JSON.stringify(services.capabilities.navLinks, null, 2)} -
+
, canvasElement ); diff --git a/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx b/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx new file mode 100644 index 00000000000..f6d18f08d05 --- /dev/null +++ b/src/plugins/discover/public/application/view_components/canvas/top_nav.tsx @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect, useState } from 'react'; +import { i18n } from '@osd/i18n'; +import { AppMountParameters } from '../../../../../../core/public'; +import { NEW_DISCOVER_APP, PLUGIN_ID } from '../../../../common'; +import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; +import { DiscoverServices } from '../../../build_services'; +import { IndexPattern } from '../../../opensearch_dashboards_services'; + +export interface TopNavProps { + opts: { + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + }; +} + +export const TopNav = ({ opts }: TopNavProps) => { + const { + services: { + uiSettings, + navigation: { + ui: { TopNavMenu }, + }, + core: { + application: { navigateToApp }, + }, + data, + }, + } = useOpenSearchDashboards(); + const [indexPatterns, setIndexPatterns] = useState(undefined); + + useEffect(() => { + if (uiSettings.get(NEW_DISCOVER_APP) === false) { + const path = window.location.hash; + navigateToApp('discoverLegacy', { + replace: true, + path, + }); + } + + return () => {}; + }, [navigateToApp, uiSettings]); + + useEffect(() => { + const getDefaultIndexPattern = async () => { + await data.indexPatterns.ensureDefaultIndexPattern(); + const indexPattern = await data.indexPatterns.getDefault(); + + setIndexPatterns(indexPattern ? [indexPattern] : undefined); + }; + + getDefaultIndexPattern(); + }, [data.indexPatterns]); + + return ( + { + await uiSettings.set(NEW_DISCOVER_APP, false); + window.location.reload(); + }, + emphasize: true, + }, + ]} + showSearchBar + useDefaultBehaviors + setMenuMountPoint={opts.setHeaderActionMenu} + indexPatterns={indexPatterns} + /> + ); +}; diff --git a/src/plugins/discover/public/application/view_components/panel/index.tsx b/src/plugins/discover/public/application/view_components/panel/index.tsx index 9e4d9a040e2..f92e5af0bfd 100644 --- a/src/plugins/discover/public/application/view_components/panel/index.tsx +++ b/src/plugins/discover/public/application/view_components/panel/index.tsx @@ -8,11 +8,12 @@ import ReactDOM from 'react-dom'; import { ViewMountParameters } from '../../../../../data_explorer/public'; import { OpenSearchDashboardsContextProvider } from '../../../../../opensearch_dashboards_react/public'; import { DiscoverServices } from '../../../build_services'; +import { Panel } from './panel'; export const renderPanel = ({ panelElement }: ViewMountParameters, services: DiscoverServices) => { ReactDOM.render( -
Side panel
+
, panelElement ); diff --git a/src/plugins/discover/public/application/view_components/panel/panel.tsx b/src/plugins/discover/public/application/view_components/panel/panel.tsx new file mode 100644 index 00000000000..fe3b36f6e87 --- /dev/null +++ b/src/plugins/discover/public/application/view_components/panel/panel.tsx @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; + +export const Panel = () => { + return
Side Panel
; +}; diff --git a/src/plugins/discover/public/index.ts b/src/plugins/discover/public/index.ts index 191ecf14cbb..3bc00991494 100644 --- a/src/plugins/discover/public/index.ts +++ b/src/plugins/discover/public/index.ts @@ -40,3 +40,4 @@ export { SavedSearch, SavedSearchLoader, createSavedSearchesLoader } from './sav // TODO: Fix embeddable after removing Angular // export { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, SearchInput } from './application/embeddable'; export { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState } from './url_generator'; +export { NEW_DISCOVER_APP } from '../common'; diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index 34cc84ee26d..b40f76dbf2e 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -86,7 +86,7 @@ import { DiscoverUrlGenerator, } from './url_generator'; // import { SearchEmbeddableFactory } from './application/embeddable'; -import { DISCOVER_LEGACY_TOGGLE, PLUGIN_ID } from '../common'; +import { NEW_DISCOVER_APP, PLUGIN_ID } from '../common'; import { DataExplorerPluginSetup, ViewRedirectParams } from '../../data_explorer/public'; import { registerFeature } from './register_feature'; @@ -319,9 +319,10 @@ export class DiscoverPlugin }, } = await this.initializeServices(); + // This is for instances where the user navigates to the app from the application nav menu const path = window.location.hash; - const enableLegacyMode = await core.uiSettings.get(DISCOVER_LEGACY_TOGGLE); - if (enableLegacyMode) { + const v2Enabled = await core.uiSettings.get(NEW_DISCOVER_APP); + if (!v2Enabled) { navigateToApp('discoverLegacy', { replace: true, path, diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index a802897c248..2b35384c2e5 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -33,7 +33,7 @@ import { schema } from '@osd/config-schema'; import { UiSettingsParams } from 'opensearch-dashboards/server'; import { - DISCOVER_LEGACY_TOGGLE, + NEW_DISCOVER_APP, DEFAULT_COLUMNS_SETTING, SAMPLE_SIZE_SETTING, AGGS_TERMS_SIZE_SETTING, @@ -48,9 +48,9 @@ import { } from '../common'; export const uiSettings: Record = { - [DISCOVER_LEGACY_TOGGLE]: { + [NEW_DISCOVER_APP]: { name: i18n.translate('discover.advancedSettings.legacyToggleTitle', { - defaultMessage: 'Disable new discover app', + defaultMessage: 'Enable new discover app', }), value: true, description: i18n.translate('discover.advancedSettings.legacyToggleText', { diff --git a/src/plugins/discover_legacy/opensearch_dashboards.json b/src/plugins/discover_legacy/opensearch_dashboards.json index 6cc4a8930ee..6a4259a41d7 100644 --- a/src/plugins/discover_legacy/opensearch_dashboards.json +++ b/src/plugins/discover_legacy/opensearch_dashboards.json @@ -14,6 +14,14 @@ "uiActions", "visualizations" ], - "optionalPlugins": ["home", "share"], - "requiredBundles": ["opensearchDashboardsUtils", "savedObjects", "opensearchDashboardsReact"] -} + "optionalPlugins": [ + "home", + "share" + ], + "requiredBundles": [ + "opensearchDashboardsUtils", + "savedObjects", + "opensearchDashboardsReact", + "discover" + ] +} \ No newline at end of file diff --git a/src/plugins/discover_legacy/public/application/angular/discover.js b/src/plugins/discover_legacy/public/application/angular/discover.js index de244e3c44b..5c5eaecb35e 100644 --- a/src/plugins/discover_legacy/public/application/angular/discover.js +++ b/src/plugins/discover_legacy/public/application/angular/discover.js @@ -93,6 +93,7 @@ import { DOC_HIDE_TIME_COLUMN_SETTING, MODIFY_COLUMNS_ON_SWITCH, } from '../../../common'; +import { NEW_DISCOVER_APP } from '../../../../discover/public'; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -480,7 +481,23 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise }, }; + const newDiscover = { + id: 'discover-new', + label: i18n.translate('discover.localMenu.newDiscoverTitle', { + defaultMessage: 'New Discover', + }), + description: i18n.translate('discover.localMenu.newDiscoverDescription', { + defaultMessage: 'New Discover Experience', + }), + testId: 'discoverNewButton', + run: async function () { + await getServices().uiSettings.set(NEW_DISCOVER_APP, true); + window.location.reload(); + }, + }; + return [ + newDiscover, newSearch, ...(uiCapabilities.discover.save ? [saveSearch] : []), openSearch, diff --git a/src/plugins/discover_legacy/public/plugin.ts b/src/plugins/discover_legacy/public/plugin.ts index 305fe62c07e..85cd68a0779 100644 --- a/src/plugins/discover_legacy/public/plugin.ts +++ b/src/plugins/discover_legacy/public/plugin.ts @@ -56,6 +56,7 @@ import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { Start as InspectorPublicPluginStart } from 'src/plugins/inspector/public'; import { stringify } from 'query-string'; import rison from 'rison-node'; +import { NEW_DISCOVER_APP } from '../../discover/public'; import { DataPublicPluginStart, DataPublicPluginSetup, opensearchFilters } from '../../data/public'; import { SavedObjectLoader } from '../../saved_objects/public'; import { createOsdUrlTracker, url } from '../../opensearch_dashboards_utils/public'; @@ -88,6 +89,7 @@ import { } from './url_generator'; import { SearchEmbeddableFactory } from './application/embeddable'; import { AppNavLinkStatus } from '../../../core/public'; +import { ViewRedirectParams } from '../../data_explorer/public'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -318,6 +320,25 @@ export class DiscoverPlugin if (!this.initializeInnerAngular) { throw Error('Discover plugin method initializeInnerAngular is undefined'); } + + // If a user explicitly tries to access the legacy app URL + const { + core: { + application: { navigateToApp }, + }, + } = await this.initializeServices(); + const path = window.location.hash; + + const v2Enabled = core.uiSettings.get(NEW_DISCOVER_APP); + if (v2Enabled) { + navigateToApp('data-explorer', { + replace: true, + path: `/discover`, + state: { + path, + } as ViewRedirectParams, + }); + } setScopedHistory(params.history); setHeaderActionMenuMounter(params.setHeaderActionMenu); syncHistoryLocations();