[Stack Monitoring] Convert cluster listing view to React (#113585)

* [Stack Monitoring] Covnert cluster listing view to React

* Fixing localStorage
This commit is contained in:
Chris Cowan 2021-10-05 09:24:20 -06:00 committed by GitHub
parent b8cdc6fd1b
commit 141a88bf62
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 244 additions and 65 deletions

View file

@ -23,6 +23,7 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { Legacy } from '../legacy_shims';
import { useAlertsModal } from '../application/hooks/use_alerts_modal';
interface Props {
alerts: {};
@ -30,8 +31,7 @@ interface Props {
export const EnableAlertsModal: React.FC<Props> = ({ alerts }: Props) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const $injector = Legacy.shims.getAngularInjector();
const alertsEnableModalProvider: any = $injector.get('enableAlertsModal');
const alertsEnableModalProvider = useAlertsModal();
const closeModal = () => {
setIsModalVisible(false);

View file

@ -0,0 +1,54 @@
/*
* 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 { useKibana } from '../../../../../../src/plugins/kibana_react/public';
import { showAlertsToast } from '../../alerts/lib/alerts_toast';
import { ajaxErrorHandlersProvider } from '../../lib/ajax_error_handler';
export const useAlertsModal = () => {
const { services } = useKibana();
function shouldShowAlertsModal(alerts: {}) {
const modalHasBeenShown =
window.sessionStorage.getItem('ALERTS_MODAL_HAS_BEEN_SHOWN') === 'true';
const decisionMade = window.localStorage.getItem('ALERTS_MODAL_DECISION_MADE') === 'true';
if (Object.keys(alerts).length > 0) {
window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', 'true');
return false;
} else if (!modalHasBeenShown && !decisionMade) {
return true;
}
return false;
}
async function enableAlerts() {
try {
const { data } = await services.http?.post('../api/monitoring/v1/alerts/enable', {});
window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', 'true');
showAlertsToast(data);
} catch (err) {
const ajaxErrorHandlers = ajaxErrorHandlersProvider();
return ajaxErrorHandlers(err);
}
}
function notAskAgain() {
window.localStorage.setItem('ALERTS_MODAL_DECISION_MADE', 'true');
}
function hideModalForSession() {
window.sessionStorage.setItem('ALERTS_MODAL_HAS_BEEN_SHOWN', 'true');
}
return {
shouldShowAlertsModal,
enableAlerts,
notAskAgain,
hideModalForSession,
};
};

View file

@ -6,7 +6,7 @@
*/
import { useState, useEffect } from 'react';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../common/constants';
import { fetchClusters } from '../../lib/fetch_clusters';
export function useClusters(clusterUuid?: string | null, ccs?: any, codePaths?: string[]) {
const { services } = useKibana<{ data: any }>();
@ -18,47 +18,29 @@ export function useClusters(clusterUuid?: string | null, ccs?: any, codePaths?:
const [clusters, setClusters] = useState([] as any);
const [loaded, setLoaded] = useState<boolean | null>(false);
let url = '../api/monitoring/v1/clusters';
if (clusterUuid) {
url += `/${clusterUuid}`;
}
useEffect(() => {
const fetchClusters = async () => {
async function makeRequest() {
try {
const response = await services.http?.fetch(url, {
method: 'POST',
body: JSON.stringify({
ccs,
if (services.http?.fetch) {
const response = await fetchClusters({
timeRange: {
min,
max,
},
fetch: services.http.fetch,
clusterUuid,
codePaths,
}),
});
setClusters(formatClusters(response));
} catch (err) {
// TODO: handle errors
});
setClusters(response);
}
} catch (e) {
// TODO: Handle errors
} finally {
setLoaded(true);
}
};
fetchClusters();
}, [ccs, services.http, codePaths, url, min, max]);
}
makeRequest();
}, [clusterUuid, ccs, services.http, codePaths, 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;
}

View file

@ -13,6 +13,7 @@ import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/p
import { LoadingPage } from './pages/loading_page';
import { LicensePage } from './pages/license_page';
import { ClusterOverview } from './pages/cluster/overview_page';
import { ClusterListing } from './pages/home/cluster_listing';
import { MonitoringStartPluginDependencies } from '../types';
import { GlobalStateProvider } from './global_state_context';
import { ExternalConfigContext, ExternalConfig } from './external_config_context';
@ -82,9 +83,10 @@ const MonitoringApp: React.FC<{
/>
<RouteInit
path="/home"
component={Home}
component={ClusterListing}
codePaths={['all']}
fetchAllClusters={false}
fetchAllClusters={true}
unsetGlobalState={true}
/>
<RouteInit
path="/overview"
@ -217,7 +219,3 @@ const MonitoringApp: React.FC<{
</KibanaContextProvider>
);
};
const Home: React.FC<{}> = () => {
return <div>Home page (Cluster listing)</div>;
};

View file

@ -16,8 +16,8 @@ import { Overview } from '../../../components/cluster/overview';
import { ExternalConfigContext } from '../../external_config_context';
import { SetupModeRenderer, SetupModeProps } from '../../setup_mode/setup_mode_renderer';
import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../common/constants';
import { BreadcrumbContainer } from '../../hooks/use_breadcrumbs';
import { fetchClusters } from '../../../lib/fetch_clusters';
const CODE_PATHS = [CODE_PATH_ALL];
@ -54,25 +54,20 @@ export const ClusterOverview: React.FC<{}> = () => {
const getPageData = useCallback(async () => {
const bounds = services.data?.query.timefilter.timefilter.getBounds();
let url = '../api/monitoring/v1/clusters';
if (clusterUuid) {
url += `/${clusterUuid}`;
}
try {
const response = await services.http?.fetch(url, {
method: 'POST',
body: JSON.stringify({
ccs,
if (services.http?.fetch) {
const response = await fetchClusters({
fetch: services.http.fetch,
timeRange: {
min: bounds.min.toISOString(),
max: bounds.max.toISOString(),
},
ccs,
clusterUuid,
codePaths: CODE_PATHS,
}),
});
setClusters(formatClusters(response));
});
setClusters(response);
}
} catch (err) {
// TODO: handle errors
} finally {
@ -106,14 +101,3 @@ export const ClusterOverview: React.FC<{}> = () => {
</PageTemplate>
);
};
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;
}

View file

@ -0,0 +1,116 @@
/*
* 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, { useCallback, useContext, useEffect, useState } from 'react';
import { Redirect } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
// @ts-ignore
import { Listing } from '../../../components/cluster/listing';
import { EnableAlertsModal } from '../../../alerts/enable_alerts_modal';
import { GlobalStateContext } from '../../global_state_context';
import { ExternalConfigContext } from '../../external_config_context';
import { ComponentProps } from '../../route_init';
import { useTable } from '../../hooks/use_table';
import { PageTemplate, TabMenuItem } from '../page_template';
import { BreadcrumbContainer } from '../../hooks/use_breadcrumbs';
import { fetchClusters } from '../../../lib/fetch_clusters';
const pageTitle = i18n.translate('xpack.monitoring.cluster.listing.pageTitle', {
defaultMessage: 'Cluster listing',
});
const tabTitle = i18n.translate('xpack.monitoring.cluster.listing.tabTitle', {
defaultMessage: 'Clusters',
});
const getAlerts = (clusters: any[]) => {
return clusters.reduce(
(alerts, cluster) => ({ ...alerts, ...((cluster.alerts && cluster.alerts.list) || {}) }),
{}
);
};
export const ClusterListing: React.FC<ComponentProps> = () => {
const globalState = useContext(GlobalStateContext);
const externalConfig = useContext(ExternalConfigContext);
const { services } = useKibana<{ data: any }>();
const [clusters, setClusters] = useState([] as any);
const { update: updateBreadcrumbs } = useContext(BreadcrumbContainer.Context);
const fakeScope = {
$evalAsync: (fn: () => void) => fn(),
filterQuery: '', // replace with something
};
const { getPaginationTableProps } = useTable('clusters');
const { sorting, pagination, onTableChange } = getPaginationTableProps();
useEffect(() => {
updateBreadcrumbs([
{
'data-test-subj': 'clusterListingBreadcrumb',
text: tabTitle,
},
]);
}, [updateBreadcrumbs]);
const tabs: TabMenuItem[] = [
{
id: 'clusters',
label: tabTitle,
testSubj: 'clusterListingTab',
route: '/home',
},
];
const getPageData = useCallback(async () => {
const bounds = services.data?.query.timefilter.timefilter.getBounds();
try {
if (services.http?.fetch) {
const response = await fetchClusters({
fetch: services.http.fetch,
timeRange: {
min: bounds.min.toISOString(),
max: bounds.max.toISOString(),
},
ccs: globalState.ccs,
codePaths: ['all'],
});
setClusters(response);
}
} catch (err) {
// TODO: handle errors
}
}, [globalState, services.data?.query.timefilter.timefilter, services.http]);
if (globalState.save && clusters.length === 1) {
globalState.cluster_uuid = clusters[0].cluster_uuid;
globalState.save();
}
return (
<PageTemplate tabs={tabs} title={pageTitle} pageTitle={pageTitle} getPageData={getPageData}>
{clusters.length === 1 && <Redirect to={{ pathname: '/overview' }} />}
<Listing
clusters={clusters}
angular={{
scope: fakeScope,
globalState,
storage: {
get: (key: string) => window.localStorage.getItem(key),
set: (key: string, value: string) => window.localStorage.setItem(key, value),
},
showLicenseExpiration: externalConfig.showLicenseExpiration,
}}
sorting={sorting}
pagination={pagination}
onTableChange={onTableChange}
/>
<EnableAlertsModal alerts={getAlerts(clusters)} />
</PageTemplate>
);
};

View file

@ -0,0 +1,45 @@
/*
* 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 { HttpHandler } from 'kibana/public';
import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../common/constants';
interface Params {
timeRange: { min: string; max: string };
fetch: HttpHandler;
clusterUuid?: string | null;
ccs?: boolean;
codePaths?: string[];
}
export function formatClusters(clusters: any) {
return clusters.map(formatCluster);
}
export function formatCluster(cluster: any) {
if (cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID) {
cluster.cluster_name = 'Standalone Cluster';
}
return cluster;
}
export const fetchClusters = async ({ clusterUuid, timeRange, fetch, ccs, codePaths }: Params) => {
let url = '../api/monitoring/v1/clusters';
if (clusterUuid) {
url += `/${clusterUuid}`;
}
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify({
ccs,
timeRange,
codePaths,
}),
});
return formatClusters(response);
};