From 2997a69939e2ce846ded98ca0ba72cd7994ae5a9 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:16:07 -0400 Subject: [PATCH] [Stack Monitoring] Convert cluster listing view to React (#113585) (#113953) * [Stack Monitoring] Covnert cluster listing view to React * Fixing localStorage Co-authored-by: Chris Cowan --- .../public/alerts/enable_alerts_modal.tsx | 4 +- .../application/hooks/use_alerts_modal.ts | 54 ++++++++ .../public/application/hooks/use_clusters.ts | 46 +++---- .../monitoring/public/application/index.tsx | 10 +- .../pages/cluster/overview_page.tsx | 34 ++--- .../pages/home/cluster_listing.tsx | 116 ++++++++++++++++++ .../monitoring/public/lib/fetch_clusters.ts | 45 +++++++ 7 files changed, 244 insertions(+), 65 deletions(-) create mode 100644 x-pack/plugins/monitoring/public/application/hooks/use_alerts_modal.ts create mode 100644 x-pack/plugins/monitoring/public/application/pages/home/cluster_listing.tsx create mode 100644 x-pack/plugins/monitoring/public/lib/fetch_clusters.ts diff --git a/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx index 780997ca98191..3e45262610f45 100644 --- a/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx +++ b/x-pack/plugins/monitoring/public/alerts/enable_alerts_modal.tsx @@ -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 = ({ 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); diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_alerts_modal.ts b/x-pack/plugins/monitoring/public/application/hooks/use_alerts_modal.ts new file mode 100644 index 0000000000000..9a2a2b80cc40f --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/hooks/use_alerts_modal.ts @@ -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, + }; +}; diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_clusters.ts b/x-pack/plugins/monitoring/public/application/hooks/use_clusters.ts index e11317fd92bde..b4b8c21ca4d40 100644 --- a/x-pack/plugins/monitoring/public/application/hooks/use_clusters.ts +++ b/x-pack/plugins/monitoring/public/application/hooks/use_clusters.ts @@ -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(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; -} diff --git a/x-pack/plugins/monitoring/public/application/index.tsx b/x-pack/plugins/monitoring/public/application/index.tsx index 088891621923c..d7f18d5300bd4 100644 --- a/x-pack/plugins/monitoring/public/application/index.tsx +++ b/x-pack/plugins/monitoring/public/application/index.tsx @@ -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'; @@ -75,9 +76,10 @@ const MonitoringApp: React.FC<{ /> ); }; - -const Home: React.FC<{}> = () => { - return
Home page (Cluster listing)
; -}; diff --git a/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx b/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx index 9bf0bf6d14795..a0a6749e49641 100644 --- a/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/cluster/overview_page.tsx @@ -16,8 +16,8 @@ import { Overview } from '../../../components/cluster/overview'; import { ExternalConfigContext } from '../../external_config_context'; import { SetupModeRenderer } 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]; interface SetupModeProps { @@ -59,25 +59,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 { @@ -111,14 +106,3 @@ export const ClusterOverview: React.FC<{}> = () => { ); }; - -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; -} diff --git a/x-pack/plugins/monitoring/public/application/pages/home/cluster_listing.tsx b/x-pack/plugins/monitoring/public/application/pages/home/cluster_listing.tsx new file mode 100644 index 0000000000000..906db1b57f0f5 --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/pages/home/cluster_listing.tsx @@ -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 = () => { + 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 ( + + {clusters.length === 1 && } + window.localStorage.getItem(key), + set: (key: string, value: string) => window.localStorage.setItem(key, value), + }, + showLicenseExpiration: externalConfig.showLicenseExpiration, + }} + sorting={sorting} + pagination={pagination} + onTableChange={onTableChange} + /> + + + ); +}; diff --git a/x-pack/plugins/monitoring/public/lib/fetch_clusters.ts b/x-pack/plugins/monitoring/public/lib/fetch_clusters.ts new file mode 100644 index 0000000000000..41cb1ab288a17 --- /dev/null +++ b/x-pack/plugins/monitoring/public/lib/fetch_clusters.ts @@ -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); +};