Shim home app (#48715)

This commit is contained in:
Joe Reuter 2019-11-07 11:56:19 -05:00 committed by GitHub
parent 0010447f0f
commit 349bc12f1e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 339 additions and 187 deletions

View file

@ -2,6 +2,7 @@
exports[`home directories should not render directory entry when showOnHomePage is false 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody
@ -117,6 +118,7 @@ exports[`home directories should not render directory entry when showOnHomePage
exports[`home directories should render ADMIN directory entry in "Manage" panel 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody
@ -245,6 +247,7 @@ exports[`home directories should render ADMIN directory entry in "Manage" panel
exports[`home directories should render DATA directory entry in "Explore Data" panel 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody
@ -373,6 +376,7 @@ exports[`home directories should render DATA directory entry in "Explore Data" p
exports[`home isNewKibanaInstance should safely handle execeptions 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody
@ -488,6 +492,7 @@ exports[`home isNewKibanaInstance should safely handle execeptions 1`] = `
exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when there are index patterns 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody
@ -603,6 +608,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to false when t
exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when there are no index patterns 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody
@ -718,6 +724,7 @@ exports[`home isNewKibanaInstance should set isNewKibanaInstance to true when th
exports[`home should render home component 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody
@ -833,6 +840,7 @@ exports[`home should render home component 1`] = `
exports[`home welcome should show the normal home page if loading fails 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody
@ -948,6 +956,7 @@ exports[`home welcome should show the normal home page if loading fails 1`] = `
exports[`home welcome should show the normal home page if welcome screen is disabled locally 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody
@ -1073,6 +1082,7 @@ exports[`home welcome should show the welcome screen if enabled, and there are n
exports[`home welcome stores skip welcome setting if skipped 1`] = `
<EuiPage
data-test-subj="homeApp"
restrictWidth={1200}
>
<EuiPageBody

View file

@ -136,7 +136,7 @@ export class Home extends Component {
const { apmUiEnabled, mlEnabled } = this.props;
return (
<EuiPage restrictWidth={1200}>
<EuiPage restrictWidth={1200} data-test-subj="homeApp">
<EuiPageBody className="eui-displayBlock">
<EuiScreenReaderOnly>

View file

@ -18,21 +18,16 @@
*/
import React from 'react';
import { I18nProvider } from '@kbn/i18n/react';
import PropTypes from 'prop-types';
import { Home } from './home';
import { FeatureDirectory } from './feature_directory';
import { TutorialDirectory } from './tutorial_directory';
import { Tutorial } from './tutorial/tutorial';
import {
HashRouter as Router,
Switch,
Route,
} from 'react-router-dom';
import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import { getTutorial } from '../load_tutorials';
import { replaceTemplateStrings } from './tutorial/replace_template_strings';
import {
getServices
} from '../kibana_services';
import { getServices } from '../kibana_services';
export function HomeApp({ directories }) {
const {
@ -47,8 +42,9 @@ export function HomeApp({ directories }) {
const isCloudEnabled = getInjected('isCloudEnabled', false);
const apmUiEnabled = getInjected('apmUiEnabled', true);
const mlEnabled = getInjected('mlEnabled', false);
const defaultAppId = getInjected('kbnDefaultAppId', 'discover');
const renderTutorialDirectory = (props) => {
const renderTutorialDirectory = props => {
return (
<TutorialDirectory
addBasePath={addBasePath}
@ -58,7 +54,7 @@ export function HomeApp({ directories }) {
);
};
const renderTutorial = (props) => {
const renderTutorial = props => {
return (
<Tutorial
addBasePath={addBasePath}
@ -72,54 +68,48 @@ export function HomeApp({ directories }) {
};
return (
<Router>
<Switch>
<Route
path="/home/tutorial/:id"
render={renderTutorial}
/>
<Route
path="/home/tutorial_directory/:tab?"
render={renderTutorialDirectory}
/>
<Route
path="/home/feature_directory"
>
<FeatureDirectory
addBasePath={addBasePath}
directories={directories}
/>
</Route>
<Route
path="/home"
>
<Home
addBasePath={addBasePath}
directories={directories}
apmUiEnabled={apmUiEnabled}
mlEnabled={mlEnabled}
find={savedObjectsClient.find}
localStorage={localStorage}
urlBasePath={getBasePath()}
shouldShowTelemetryOptIn={shouldShowTelemetryOptIn}
setOptIn={telemetryOptInProvider.setOptIn}
fetchTelemetry={telemetryOptInProvider.fetchExample}
getTelemetryBannerId={telemetryOptInProvider.getBannerId}
/>
</Route>
</Switch>
</Router>
<I18nProvider>
<Router>
<Switch>
<Route path="/home/tutorial/:id" render={renderTutorial} />
<Route path="/home/tutorial_directory/:tab?" render={renderTutorialDirectory} />
<Route exact path="/home/feature_directory">
<FeatureDirectory addBasePath={addBasePath} directories={directories} />
</Route>
<Route exact path="/home">
<Home
addBasePath={addBasePath}
directories={directories}
apmUiEnabled={apmUiEnabled}
mlEnabled={mlEnabled}
find={savedObjectsClient.find}
localStorage={localStorage}
urlBasePath={getBasePath()}
shouldShowTelemetryOptIn={shouldShowTelemetryOptIn}
setOptIn={telemetryOptInProvider.setOptIn}
fetchTelemetry={telemetryOptInProvider.fetchExample}
getTelemetryBannerId={telemetryOptInProvider.getBannerId}
/>
</Route>
<Route path="/home">
<Redirect to={`/${defaultAppId}`} />
</Route>
</Switch>
</Router>
</I18nProvider>
);
}
HomeApp.propTypes = {
directories: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
showOnHomePage: PropTypes.bool.isRequired,
category: PropTypes.string.isRequired,
})),
directories: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
showOnHomePage: PropTypes.bool.isRequired,
category: PropTypes.string.isRequired,
})
),
};

View file

@ -1,5 +0,0 @@
<home-app
directories="directories"
recently-accessed="recentlyAccessed"
data-test-subj="homeApp"
/>

View file

@ -1,61 +0,0 @@
/*
* 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 { getServices } from './kibana_services';
import template from './home_ng_wrapper.html';
import {
HomeApp
} from './components/home_app';
import { i18n } from '@kbn/i18n';
const { wrapInI18nContext, uiRoutes, uiModules } = getServices();
const app = uiModules.get('apps/home', []);
app.directive('homeApp', function (reactDirective) {
return reactDirective(wrapInI18nContext(HomeApp));
});
const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' });
function getRoute() {
return {
template,
resolve: {
directories: () => getServices().getFeatureCatalogueEntries()
},
controller($scope, $route) {
const { chrome, addBasePath } = getServices();
$scope.directories = $route.current.locals.directories;
$scope.recentlyAccessed = chrome.recentlyAccessed.get().map(item => {
item.link = addBasePath(item.link);
return item;
});
},
k7Breadcrumbs: () => [
{ text: homeTitle },
]
};
}
// All routing will be handled inside HomeApp via react, we just need to make sure angular doesn't
// redirect us to the default page by encountering a url it isn't marked as being able to handle.
uiRoutes.when('/home', getRoute());
uiRoutes.when('/home/feature_directory', getRoute());
uiRoutes.when('/home/tutorial_directory/:tab?', getRoute());
uiRoutes.when('/home/tutorial/:id', getRoute());

View file

@ -0,0 +1,80 @@
/*
* 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 { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue';
import { npSetup, npStart } from 'ui/new_platform';
import chrome from 'ui/chrome';
import { IPrivate } from 'ui/private';
import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin';
import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public';
import { start as data } from '../../../data/public/legacy';
import { TelemetryOptInProvider } from '../../../telemetry/public/services';
import { localApplicationService } from '../local_application_service';
export const trackUiMetric = createUiStatsReporter('Kibana_home');
/**
* Get dependencies relying on the global angular context.
* They also have to get resolved together with the legacy imports above
*/
async function getAngularDependencies(): Promise<LegacyAngularInjectedDependencies> {
const injector = await chrome.dangerouslyGetActiveInjector();
const Private = injector.get<IPrivate>('Private');
const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled');
const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner');
const telemetryOptInProvider = Private(TelemetryOptInProvider);
return {
telemetryOptInProvider,
shouldShowTelemetryOptIn:
telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(),
};
}
let copiedLegacyCatalogue = false;
(async () => {
const instance = new HomePlugin();
instance.setup(npSetup.core, {
__LEGACY: {
trackUiMetric,
metadata: npStart.core.injectedMetadata.getLegacyMetadata(),
METRIC_TYPE,
getFeatureCatalogueEntries: async () => {
if (!copiedLegacyCatalogue) {
const injector = await chrome.dangerouslyGetActiveInjector();
const Private = injector.get<IPrivate>('Private');
// Merge legacy registry with new registry
(Private(FeatureCatalogueRegistryProvider as any) as any).inTitleOrder.map(
npSetup.plugins.feature_catalogue.register
);
copiedLegacyCatalogue = true;
}
return npStart.plugins.feature_catalogue.get();
},
getAngularDependencies,
localApplicationService,
},
});
instance.start(npStart.core, {
data,
});
})();

View file

@ -17,69 +17,63 @@
* under the License.
*/
// @ts-ignore
import { toastNotifications, banners } from 'ui/notify';
import { kfetch } from 'ui/kfetch';
import chrome from 'ui/chrome';
import {
ChromeStart,
DocLinksStart,
HttpStart,
LegacyNavLink,
NotificationsSetup,
OverlayStart,
SavedObjectsClientContract,
UiSettingsClientContract,
UiSettingsState,
} from 'kibana/public';
import { UiStatsMetricType } from '@kbn/analytics';
import { FeatureCatalogueEntry } from '../../../../../plugins/feature_catalogue/public';
import { wrapInI18nContext } from 'ui/i18n';
// @ts-ignore
import { uiModules as modules } from 'ui/modules';
import routes from 'ui/routes';
import { npSetup, npStart } from 'ui/new_platform';
import { IPrivate } from 'ui/private';
import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue';
import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public';
import { TelemetryOptInProvider } from '../../../telemetry/public/services';
import { start as data } from '../../../data/public/legacy';
let shouldShowTelemetryOptIn: boolean;
let telemetryOptInProvider: any;
export function getServices() {
return {
getInjected: npStart.core.injectedMetadata.getInjectedVar,
metadata: npStart.core.injectedMetadata.getLegacyMetadata(),
docLinks: npStart.core.docLinks,
uiRoutes: routes,
uiModules: modules,
savedObjectsClient: npStart.core.savedObjects.client,
chrome: npStart.core.chrome,
uiSettings: npStart.core.uiSettings,
addBasePath: npStart.core.http.basePath.prepend,
getBasePath: npStart.core.http.basePath.get,
indexPatternService: data.indexPatterns.indexPatterns,
shouldShowTelemetryOptIn,
telemetryOptInProvider,
getFeatureCatalogueEntries: async () => {
const injector = await chrome.dangerouslyGetActiveInjector();
const Private = injector.get<IPrivate>('Private');
// Merge legacy registry with new registry
(Private(FeatureCatalogueRegistryProvider as any) as any).inTitleOrder.map(
npSetup.plugins.feature_catalogue.register
);
return npStart.plugins.feature_catalogue.get();
},
trackUiMetric: createUiStatsReporter('Kibana_home'),
METRIC_TYPE,
toastNotifications,
banners,
kfetch,
wrapInI18nContext,
export interface HomeKibanaServices {
indexPatternService: any;
getFeatureCatalogueEntries: () => Promise<readonly FeatureCatalogueEntry[]>;
metadata: {
app: unknown;
bundleId: string;
nav: LegacyNavLink[];
version: string;
branch: string;
buildNum: number;
buildSha: string;
basePath: string;
serverName: string;
devMode: boolean;
uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined };
};
getInjected: (name: string, defaultValue?: any) => unknown;
chrome: ChromeStart;
telemetryOptInProvider: any;
uiSettings: UiSettingsClientContract;
http: HttpStart;
savedObjectsClient: SavedObjectsClientContract;
toastNotifications: NotificationsSetup['toasts'];
banners: OverlayStart['banners'];
METRIC_TYPE: any;
trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void;
getBasePath: () => string;
shouldShowTelemetryOptIn: boolean;
docLinks: DocLinksStart;
addBasePath: (url: string) => string;
}
modules.get('kibana').run((Private: IPrivate) => {
const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled');
const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner');
let services: HomeKibanaServices | null = null;
telemetryOptInProvider = Private(TelemetryOptInProvider);
shouldShowTelemetryOptIn =
telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn();
});
export function setServices(newServices: HomeKibanaServices) {
services = newServices;
}
export function getServices() {
if (!services) {
throw new Error(
'Kibana services not set - are you trying to import this module from outside of the home app?'
);
}
return services;
}

View file

@ -0,0 +1,103 @@
/*
* 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 { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public';
import { UiStatsMetricType } from '@kbn/analytics';
import { DataStart } from '../../../data/public';
import { LocalApplicationService } from '../local_application_service';
import { setServices } from './kibana_services';
import { FeatureCatalogueEntry } from '../../../../../plugins/feature_catalogue/public';
export interface LegacyAngularInjectedDependencies {
telemetryOptInProvider: any;
shouldShowTelemetryOptIn: boolean;
}
export interface HomePluginStartDependencies {
data: DataStart;
}
export interface HomePluginSetupDependencies {
__LEGACY: {
trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void;
METRIC_TYPE: any;
metadata: {
app: unknown;
bundleId: string;
nav: LegacyNavLink[];
version: string;
branch: string;
buildNum: number;
buildSha: string;
basePath: string;
serverName: string;
devMode: boolean;
uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined };
};
getFeatureCatalogueEntries: () => Promise<readonly FeatureCatalogueEntry[]>;
getAngularDependencies: () => Promise<LegacyAngularInjectedDependencies>;
localApplicationService: LocalApplicationService;
};
}
export class HomePlugin implements Plugin {
private dataStart: DataStart | null = null;
private savedObjectsClient: any = null;
setup(
core: CoreSetup,
{
__LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices },
}: HomePluginSetupDependencies
) {
localApplicationService.register({
id: 'home',
title: 'Home',
mount: async ({ core: contextCore }, params) => {
const angularDependencies = await getAngularDependencies();
setServices({
...legacyServices,
http: contextCore.http,
toastNotifications: core.notifications.toasts,
banners: contextCore.overlays.banners,
getInjected: core.injectedMetadata.getInjectedVar,
docLinks: contextCore.docLinks,
savedObjectsClient: this.savedObjectsClient!,
chrome: contextCore.chrome,
uiSettings: core.uiSettings,
addBasePath: core.http.basePath.prepend,
getBasePath: core.http.basePath.get,
indexPatternService: this.dataStart!.indexPatterns.indexPatterns,
...angularDependencies,
});
const { renderApp } = await import('./render_app');
return await renderApp(params.element);
},
});
}
start(core: CoreStart, { data }: HomePluginStartDependencies) {
// TODO is this really the right way? I though the app context would give us those
this.dataStart = data;
this.savedObjectsClient = core.savedObjects.client;
}
stop() {}
}

View file

@ -0,0 +1,38 @@
/*
* 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 React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { HomeApp } from './components/home_app';
import { getServices } from './kibana_services';
export const renderApp = async (element: HTMLElement) => {
const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' });
const { getFeatureCatalogueEntries, chrome } = getServices();
const directories = await getFeatureCatalogueEntries();
chrome.setBreadcrumbs([{ text: homeTitle }]);
render(<HomeApp directories={directories} />, element);
return () => {
unmountComponentAtNode(element);
};
};

View file

@ -26,11 +26,11 @@ function clearIndexPatternsCache() {
}
export async function listSampleDataSets() {
return await getServices().kfetch({ method: 'GET', pathname: sampleDataUrl });
return await getServices().http.get(sampleDataUrl);
}
export async function installSampleDataSet(id, sampleDataDefaultIndex) {
await getServices().kfetch({ method: 'POST', pathname: `${sampleDataUrl}/${id}` });
await getServices().http.post(`${sampleDataUrl}/${id}`);
if (getServices().uiSettings.isDefault('defaultIndex')) {
getServices().uiSettings.set('defaultIndex', sampleDataDefaultIndex);
@ -40,7 +40,7 @@ export async function installSampleDataSet(id, sampleDataDefaultIndex) {
}
export async function uninstallSampleDataSet(id, sampleDataDefaultIndex) {
await getServices().kfetch({ method: 'DELETE', pathname: `${sampleDataUrl}/${id}` });
await getServices().http.delete(`${sampleDataUrl}/${id}`);
const uiSettings = getServices().uiSettings;

View file

@ -290,6 +290,9 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => (
});
$rootScope.$on('$routeChangeSuccess', () => {
if (isDummyWrapperRoute($route)) {
return;
}
const current = $route.current || {};
if (helpExtensionSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) {