Skip to content

Commit

Permalink
[Stack Monitoring] Add initial react app (#109218)
Browse files Browse the repository at this point in the history
* Add feature toggle

* Add basic react app with router and simple loading page

* Add title hook

* Add loading page with page template and clusters hook

* fix types

* fix tests

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
estermv and kibanamachine authored Aug 23, 2021
1 parent 69f2a0e commit ec7889a
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 16 deletions.
65 changes: 65 additions & 0 deletions x-pack/plugins/monitoring/public/application/hooks/use_clusters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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 { useState, useEffect } from 'react';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../common/constants';

export function useClusters(codePaths?: string[], fetchAllClusters?: boolean, ccs?: any) {
const clusterUuid = fetchAllClusters ? null : '';
const { services } = useKibana<{ data: any }>();

const bounds = services.data?.query.timefilter.timefilter.getBounds();
const [min] = useState(bounds.min.toISOString());
const [max] = useState(bounds.max.toISOString());

const [clusters, setClusters] = useState([]);
const [loaded, setLoaded] = useState<boolean | null>(false);

let url = '../api/monitoring/v1/clusters';
if (clusterUuid) {
url += `/${clusterUuid}`;
}

useEffect(() => {
const fetchClusters = async () => {
try {
const response = await services.http?.fetch(url, {
method: 'POST',
body: JSON.stringify({
ccs,
timeRange: {
min,
max,
},
codePaths,
}),
});

setClusters(formatClusters(response));
} catch (err) {
// TODO: handle errors
} finally {
setLoaded(null);
}
};

fetchClusters();
}, [ccs, services.http, codePaths, url, min, max]);

return { clusters, loaded };
}

function formatClusters(clusters: any) {
return clusters.map(formatCluster);
}

function formatCluster(cluster: any) {
if (cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID) {
cluster.cluster_name = 'Standalone Cluster';
}
return cluster;
}
24 changes: 24 additions & 0 deletions x-pack/plugins/monitoring/public/application/hooks/use_title.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { get } from 'lodash';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';

// TODO: verify that works for all pages
export function useTitle(cluster: string, suffix: string) {
const { services } = useKibana();
let clusterName = get(cluster, 'cluster_name');
clusterName = clusterName ? `- ${clusterName}` : '';
suffix = suffix ? `- ${suffix}` : '';

services.chrome?.docTitle.change(
i18n.translate('xpack.monitoring.stackMonitoringDocTitle', {
defaultMessage: 'Stack Monitoring {clusterName} {suffix}',
values: { clusterName, suffix },
})
);
}
61 changes: 61 additions & 0 deletions x-pack/plugins/monitoring/public/application/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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 { CoreStart, AppMountParameters } from 'kibana/public';
import React from 'react';
import ReactDOM from 'react-dom';
import { Route, Switch, Redirect, HashRouter } from 'react-router-dom';
import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public';
import { LoadingPage } from './pages/loading_page';
import { MonitoringStartPluginDependencies } from '../types';

export const renderApp = (
core: CoreStart,
plugins: MonitoringStartPluginDependencies,
{ element }: AppMountParameters
) => {
ReactDOM.render(<MonitoringApp core={core} plugins={plugins} />, element);

return () => {
ReactDOM.unmountComponentAtNode(element);
};
};

const MonitoringApp: React.FC<{
core: CoreStart;
plugins: MonitoringStartPluginDependencies;
}> = ({ core, plugins }) => {
return (
<KibanaContextProvider services={{ ...core, ...plugins }}>
<HashRouter>
<Switch>
<Route path="/loading" component={LoadingPage} />
<Route path="/no-data" component={NoData} />
<Route path="/home" component={Home} />
<Route path="/overview" component={ClusterOverview} />
<Redirect
to={{
pathname: '/loading',
}}
/>
</Switch>
</HashRouter>
</KibanaContextProvider>
);
};

const NoData: React.FC<{}> = () => {
return <div>No data page</div>;
};

const Home: React.FC<{}> = () => {
return <div>Home page (Cluster listing)</div>;
};

const ClusterOverview: React.FC<{}> = () => {
return <div>Cluster overview page</div>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 React from 'react';
import { Redirect } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { PageTemplate } from './page_template';
import { PageLoading } from '../../components';
import { useClusters } from '../hooks/use_clusters';
import { CODE_PATH_ELASTICSEARCH } from '../../../common/constants';

const CODE_PATHS = [CODE_PATH_ELASTICSEARCH];

export const LoadingPage = () => {
const { clusters, loaded } = useClusters(CODE_PATHS, true);
const title = i18n.translate('xpack.monitoring.loading.pageTitle', {
defaultMessage: 'Loading',
});

return (
<PageTemplate title={title}>
{loaded === false ? <PageLoading /> : renderRedirections(clusters)}
</PageTemplate>
);
};

const renderRedirections = (clusters: any) => {
if (!clusters || !clusters.length) {
return <Redirect to="/no-data" />;
}
if (clusters.length === 1) {
// Bypass the cluster listing if there is just 1 cluster
return <Redirect to="/overview" />;
}

return <Redirect to="/home" />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 React from 'react';
import { useTitle } from '../hooks/use_title';

interface PageTemplateProps {
title: string;
children: React.ReactNode;
}

export const PageTemplate = ({ title, children }: PageTemplateProps) => {
useTitle('', title);

return <div>{children}</div>;
};
8 changes: 8 additions & 0 deletions x-pack/plugins/monitoring/public/components/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export const PageLoading: FunctionComponent<Props>;
40 changes: 24 additions & 16 deletions x-pack/plugins/monitoring/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export class MonitoringPlugin
mount: async (params: AppMountParameters) => {
const [coreStart, pluginsStart] = await core.getStartServices();
const { AngularApp } = await import('./angular');
const externalConfig = this.getExternalConfig();
const deps: MonitoringStartPluginDependencies = {
navigation: pluginsStart.navigation,
kibanaLegacy: pluginsStart.kibanaLegacy,
Expand All @@ -102,27 +103,33 @@ export class MonitoringPlugin
data: pluginsStart.data,
isCloud: Boolean(plugins.cloud?.isCloudEnabled),
pluginInitializerContext: this.initializerContext,
externalConfig: this.getExternalConfig(),
externalConfig,
triggersActionsUi: pluginsStart.triggersActionsUi,
usageCollection: plugins.usageCollection,
appMountParameters: params,
};

const monitoringApp = new AngularApp(deps);
const removeHistoryListener = params.history.listen((location) => {
if (location.pathname === '' && location.hash === '') {
monitoringApp.applyScope();
}
});

const removeHashChange = this.setInitialTimefilter(deps);
return () => {
if (removeHashChange) {
removeHashChange();
}
removeHistoryListener();
monitoringApp.destroy();
};
const config = Object.fromEntries(externalConfig);
if (config.renderReactApp) {
const { renderApp } = await import('./application');
return renderApp(coreStart, pluginsStart, params);
} else {
const monitoringApp = new AngularApp(deps);
const removeHistoryListener = params.history.listen((location) => {
if (location.pathname === '' && location.hash === '') {
monitoringApp.applyScope();
}
});

const removeHashChange = this.setInitialTimefilter(deps);
return () => {
if (removeHashChange) {
removeHashChange();
}
removeHistoryListener();
monitoringApp.destroy();
};
}
},
};

Expand Down Expand Up @@ -163,6 +170,7 @@ export class MonitoringPlugin
['showLicenseExpiration', monitoring.ui.show_license_expiration],
['showCgroupMetricsElasticsearch', monitoring.ui.container.elasticsearch.enabled],
['showCgroupMetricsLogstash', monitoring.ui.container.logstash.enabled],
['renderReactApp', monitoring.ui.render_react_app],
];
}

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/monitoring/server/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ describe('config schema', () => {
"index": "metricbeat-*",
},
"min_interval_seconds": 10,
"render_react_app": false,
"show_license_expiration": true,
},
}
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/monitoring/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const configSchema = schema.object({
}),
min_interval_seconds: schema.number({ defaultValue: 10 }),
show_license_expiration: schema.boolean({ defaultValue: true }),
render_react_app: schema.boolean({ defaultValue: false }),
}),
kibana: schema.object({
collection: schema.object({
Expand Down

0 comments on commit ec7889a

Please sign in to comment.