diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index 49130e16baa84..cbc9a8add528a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -115,7 +115,8 @@ uiRoutes template: indexTemplate, reloadOnSearch: false, resolve: { - ip: function (Promise, indexPatterns, config, Private) { + ip: function (Promise, /*indexPatterns, config,*/ Private) { + return null; const State = Private(StateProvider); return indexPatterns.getCache().then((savedObjects)=> { /** @@ -142,7 +143,8 @@ uiRoutes }); }); }, - savedSearch: function (redirectWhenMissing, savedSearches, $route) { + savedSearch: function (/*redirectWhenMissing, savedSearches,*/ $route) { + return null; const savedSearchId = $route.current.params.id; return savedSearches.get(savedSearchId) .then((savedSearch) => { diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/legacy/core_plugins/kibana/public/discover/index.ts index 80ad117d59bf1..ce0d70d0ed09a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/index.ts @@ -19,6 +19,7 @@ import { PluginInitializer, PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { DiscoverPlugin, DiscoverSetup, DiscoverStart } from './plugin'; +import { localApplicationService } from '../local_application_service'; import { start as data } from '../../../../../../src/legacy/core_plugins/data/public/legacy'; // Core will be looking for this when loading our plugin in the new platform export const plugin: PluginInitializer = ( @@ -28,5 +29,5 @@ export const plugin: PluginInitializer = ( }; const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core, { data, npData: npStart.plugins.data }); +export const setup = pluginInstance.setup(npSetup.core, { data, npData: npStart.plugins.data, localApplicationService }); export const start = pluginInstance.start(npStart.core, { data, npData: npStart.plugins.data }); diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts index 393536456b9bf..a8b1030c8b153 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -19,6 +19,7 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; import { i18n } from '@kbn/i18n'; +import { LocalApplicationService } from '../local_application_service'; import { FeatureCatalogueRegistryProvider, @@ -52,8 +53,9 @@ function registerFeature() { export type DiscoverSetup = {}; // eslint-disable-next-line @typescript-eslint/prefer-interface export type DiscoverStart = {}; -// eslint-disable-next-line @typescript-eslint/prefer-interface -export type DiscoverSetupDeps = {}; +export interface DiscoverSetupDeps { + localApplicationService: LocalApplicationService; +}; // eslint-disable-next-line @typescript-eslint/prefer-interface export type DiscoverStartDeps = {}; @@ -61,7 +63,9 @@ export class DiscoverPlugin implements Plugin { constructor(initializerContext: PluginInitializerContext) {} setup(core: CoreSetup, plugins: DiscoverSetupDeps): DiscoverSetup { registerFeature(); - core.application.register({ + // TODO once there is a central platform router in place, + // switch this back to core.application.register + plugins.localApplicationService.register({ id: 'discover', title: 'Discover', order: -1004, diff --git a/src/legacy/core_plugins/kibana/public/discover/render_app.ts b/src/legacy/core_plugins/kibana/public/discover/render_app.ts index b66562ba01011..d4bb59e1c0ac9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/discover/render_app.ts @@ -106,8 +106,8 @@ export async function renderApp( }; */ - getDiscoverModule(); - await import('./angular'); + const app = getDiscoverModule(); + require('./angular'); const $injector = mountDiscoverApp(appBasePath, element); return () => $injector.get('$rootScope').$destroy(); } diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 6c809e84c8c84..e641f5b163a61 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -59,6 +59,10 @@ import 'ui/agg_types'; import { showAppRedirectNotification } from 'ui/notify'; import 'leaflet'; +import { localApplicationService } from './local_application_service'; + +localApplicationService.registerWithAngularRouter(routes); + routes.enable(); diff --git a/src/legacy/core_plugins/kibana/public/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service.ts new file mode 100644 index 0000000000000..528107a45368e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service.ts @@ -0,0 +1,89 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ApplicationSetup, App, AppUnmount } from 'kibana/public'; +import { UIRoutes } from 'ui/routes'; +import { IScope } from 'angular'; +import { npStart } from 'ui/new_platform'; +import { htmlIdGenerator } from '@elastic/eui'; + +/** + * To be able to migrate and shim parts of the Kibana app plugin + * while still running some parts of it in the legacy world, this + * service emulates the core application service while using the global + * angular router to switch between apps without page reload. + * + * The id of the apps is used as prefix of the route - when switching between + * to apps, the current application is torn down. + * + * This service becomes unnecessary once the platform provides a central + * router that handles switching between applications without page reload. + */ +export interface LocalApplicationService { + register: ApplicationSetup['register']; + registerWithAngularRouter: (routeManager: UIRoutes) => void; +} + +const apps: App[] = []; +const idGenerator = htmlIdGenerator('kibanaAppLocalApp'); + +let currentlyActiveApp: string | null = null; +let currentlyActiveMountpoint: Element | null = null; + +export const localApplicationService: LocalApplicationService = { + register(app) { + apps.push(app); + }, + registerWithAngularRouter(angularRouteManager: UIRoutes) { + apps.forEach(app => { + const wrapperElementId = idGenerator(); + const routeConfig = { + // marker for stuff operating on the route data. + // This can be used to not execute some operations because + // the route is not actually doing something besides serving + // as a wrapper for the actual inner-angular routes + outerAngularWrapperRoute: true, + template: `
`, + controller($scope: IScope) { + const element = document.getElementById(wrapperElementId)!; + if (currentlyActiveMountpoint) { + // re-append the element containing the active app to the DOM + // because the route change causes angular to throw away the current + // template + element.appendChild(currentlyActiveMountpoint); + } + // do not bootstrap the app again if just the tail changed + if (currentlyActiveApp === app.id) { + return; + } + currentlyActiveApp = app.id; + // controller itself is not allowed to be async, use inner IIFE + (async () => { + const onUnmount = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); + currentlyActiveMountpoint = element.firstElementChild; + $scope.$on('$destroy', () => { + onUnmount(); + }); + })(); + }, + }; + angularRouteManager.when(`/${app.id}/:tail*?`, routeConfig); + }); + }, +};