mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Stack Monitoring][Angular removal] No-data page (#110432)
* Basic no-data page * Rename to NoDataPage * Add getData property to pass into EuiSuperDatePicker from pages * Wire getData and redirect for no data page * Draft port of isLoading & model updating * Add todo on handling checkers * Switch to model as state object * Add checkers * Porting enabler * Fix build checks * Attempting to smooth out enablement * Clean up CI errors * Fix breadcrumbs * Fix linter warning * Fix checkers dependency (I hope) * Hook up catchReason * Add a stub for react setup mode * Clean warnings * Fix toggleSetupMode by calling initSetupModeState first * Translating checker strings * typo on "xpack" * Move isCollection/reason check in NoData This replicates how the angular app did selective re-rendering of the react component, but while still being able to render the component. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
fd0f42386f
commit
d37ad90e53
6 changed files with 311 additions and 7 deletions
|
@ -18,6 +18,7 @@ import { GlobalStateProvider } from './global_state_context';
|
|||
import { ExternalConfigContext, ExternalConfig } from './external_config_context';
|
||||
import { createPreserveQueryHistory } from './preserve_query_history';
|
||||
import { RouteInit } from './route_init';
|
||||
import { NoDataPage } from './pages/no_data';
|
||||
import { ElasticsearchOverviewPage } from './pages/elasticsearch/overview';
|
||||
import { CODE_PATH_ELASTICSEARCH } from '../../common/constants';
|
||||
import { MonitoringTimeContainer } from './hooks/use_monitoring_time';
|
||||
|
@ -54,7 +55,7 @@ const MonitoringApp: React.FC<{
|
|||
<BreadcrumbContainer.Provider history={history}>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path="/no-data" component={NoData} />
|
||||
<Route path="/no-data" component={NoDataPage} />
|
||||
<Route path="/loading" component={LoadingPage} />
|
||||
<RouteInit
|
||||
path="/license"
|
||||
|
@ -98,10 +99,6 @@ const MonitoringApp: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
const NoData: React.FC<{}> = () => {
|
||||
return <div>No data page</div>;
|
||||
};
|
||||
|
||||
const Home: React.FC<{}> = () => {
|
||||
return <div>Home page (Cluster listing)</div>;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// From x-pack/plugins/monitoring/public/lib/elasticsearch_settings/enabler.js
|
||||
export class Enabler {
|
||||
http: any;
|
||||
updateModel: any;
|
||||
|
||||
constructor(http: any, updateModel: (properties: any) => void) {
|
||||
this.http = http;
|
||||
this.updateModel = updateModel;
|
||||
}
|
||||
|
||||
async enableCollectionInterval() {
|
||||
try {
|
||||
this.updateModel({ isCollectionIntervalUpdating: true });
|
||||
|
||||
await this.http.fetch('../api/monitoring/v1/elasticsearch_settings/set/collection_interval', {
|
||||
method: 'PUT',
|
||||
});
|
||||
this.updateModel({
|
||||
isCollectionIntervalUpdated: true,
|
||||
isCollectionIntervalUpdating: false,
|
||||
});
|
||||
} catch (err) {
|
||||
this.updateModel({
|
||||
errors: (err as any).data,
|
||||
isCollectionIntervalUpdated: false,
|
||||
isCollectionIntervalUpdating: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async enableCollectionEnabled() {
|
||||
try {
|
||||
this.updateModel({ isCollectionEnabledUpdating: true });
|
||||
await this.http.fetch('../api/monitoring/v1/elasticsearch_settings/set/collection_enabled', {
|
||||
method: 'PUT',
|
||||
});
|
||||
|
||||
this.updateModel({
|
||||
isCollectionEnabledUpdated: true,
|
||||
isCollectionEnabledUpdating: false,
|
||||
});
|
||||
} catch (err) {
|
||||
this.updateModel({
|
||||
errors: (err as any).data,
|
||||
isCollectionEnabledUpdated: false,
|
||||
isCollectionEnabledUpdating: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 { NoDataPage } from './no_data_page';
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* 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, useState } from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// @ts-ignore
|
||||
import { NoData } from '../../../components/no_data';
|
||||
import { PageTemplate } from '../page_template';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { CODE_PATH_LICENSE, STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../common/constants';
|
||||
import { Legacy } from '../../../legacy_shims';
|
||||
import { Enabler } from './enabler';
|
||||
import { BreadcrumbContainer } from '../../hooks/use_breadcrumbs';
|
||||
import { initSetupModeState } from '../../setup_mode/setup_mode';
|
||||
import { GlobalStateContext } from '../../global_state_context';
|
||||
|
||||
const CODE_PATHS = [CODE_PATH_LICENSE];
|
||||
|
||||
interface NoDataPageSetupDeps {
|
||||
http: any;
|
||||
data: any;
|
||||
}
|
||||
|
||||
interface SettingsChecker {
|
||||
message: string;
|
||||
api: string;
|
||||
next?: SettingsChecker;
|
||||
}
|
||||
|
||||
const clusterCheckers: SettingsChecker[] = [
|
||||
{
|
||||
message: i18n.translate('xpack.monitoring.noData.checker.clusterSettings', {
|
||||
defaultMessage: 'Checking cluster settings API on production cluster',
|
||||
}),
|
||||
api: '../api/monitoring/v1/elasticsearch_settings/check/cluster',
|
||||
},
|
||||
{
|
||||
message: i18n.translate('xpack.monitoring.noData.checker.nodesSettings', {
|
||||
defaultMessage: 'Checking nodes settings API on production cluster',
|
||||
}),
|
||||
api: '../api/monitoring/v1/elasticsearch_settings/check/nodes',
|
||||
},
|
||||
];
|
||||
|
||||
export const NoDataPage = () => {
|
||||
const title = i18n.translate('xpack.monitoring.noData.routeTitle', {
|
||||
defaultMessage: 'Setup Monitoring',
|
||||
});
|
||||
|
||||
const { services } = useKibana<NoDataPageSetupDeps>();
|
||||
const [shouldRedirect, setShouldRedirect] = useState(false);
|
||||
|
||||
const [model, setModel] = useState({
|
||||
errors: [], // errors can happen from trying to check or set ES settings
|
||||
checkMessage: null, // message to show while waiting for api response
|
||||
isLoading: true, // flag for in-progress state of checking for no data reason
|
||||
isCollectionEnabledUpdating: false, // flags to indicate whether to show a spinner while waiting for ajax
|
||||
isCollectionEnabledUpdated: false,
|
||||
isCollectionIntervalUpdating: false,
|
||||
isCollectionIntervalUpdated: false,
|
||||
} as any);
|
||||
|
||||
const { update: updateBreadcrumbs } = useContext(BreadcrumbContainer.Context);
|
||||
updateBreadcrumbs([
|
||||
{
|
||||
'data-test-subj': 'breadcrumbClusters',
|
||||
text: 'Clusters',
|
||||
href: '#/home',
|
||||
ignoreGlobalState: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const globalState = useContext(GlobalStateContext);
|
||||
initSetupModeState(globalState, services.http);
|
||||
|
||||
// From x-pack/plugins/monitoring/public/views/no_data/model_updater.js
|
||||
const updateModel = useCallback(
|
||||
(properties: any) => {
|
||||
setModel((previousModel: any) => {
|
||||
const updated = { ...previousModel };
|
||||
const keys = Object.keys(properties);
|
||||
|
||||
keys.forEach((key) => {
|
||||
if (Array.isArray(updated[key])) {
|
||||
updated[key].push(properties[key]);
|
||||
} else {
|
||||
updated[key] = properties[key];
|
||||
}
|
||||
});
|
||||
|
||||
return updated;
|
||||
});
|
||||
},
|
||||
[setModel]
|
||||
);
|
||||
|
||||
const getPageData = useCallback(async () => {
|
||||
let catchReason;
|
||||
try {
|
||||
const clusters = await getClusters(services);
|
||||
|
||||
if (clusters && clusters.length) {
|
||||
setShouldRedirect(true);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
if (err && err.status === 503) {
|
||||
catchReason = {
|
||||
property: 'custom',
|
||||
message: err.data.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (catchReason) {
|
||||
updateModel({ reason: catchReason });
|
||||
} else {
|
||||
await startChecks(clusterCheckers, services.http, updateModel);
|
||||
}
|
||||
}, [services, updateModel]);
|
||||
|
||||
const enabler = new Enabler(services.http, updateModel);
|
||||
|
||||
return (
|
||||
<PageTemplate title={title} getPageData={getPageData}>
|
||||
{shouldRedirect ? (
|
||||
<Redirect to="/home" />
|
||||
) : (
|
||||
<NoData {...model} enabler={enabler} isCloudEnabled={Legacy.shims.isCloud} />
|
||||
)}
|
||||
</PageTemplate>
|
||||
);
|
||||
};
|
||||
|
||||
async function getClusters(services: NoDataPageSetupDeps): Promise<any[]> {
|
||||
const url = '../api/monitoring/v1/clusters';
|
||||
const bounds = services.data?.query.timefilter.timefilter.getBounds();
|
||||
const min = bounds.min.toISOString();
|
||||
const max = bounds.max.toISOString();
|
||||
|
||||
const response = await services.http?.fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
css: undefined,
|
||||
timeRange: {
|
||||
min,
|
||||
max,
|
||||
},
|
||||
codePaths: CODE_PATHS,
|
||||
}),
|
||||
});
|
||||
|
||||
return formatClusters(response);
|
||||
}
|
||||
|
||||
// From x-pack/plugins/monitoring/public/lib/elasticsearch_settings/start_checks.js
|
||||
const mapCheckers = (_checkers: SettingsChecker[]) => {
|
||||
return _checkers.map((current, checkerIndex) => {
|
||||
const next = _checkers[checkerIndex + 1];
|
||||
if (next !== undefined) {
|
||||
current.next = next;
|
||||
}
|
||||
|
||||
return current;
|
||||
});
|
||||
};
|
||||
|
||||
// From x-pack/plugins/monitoring/public/lib/elasticsearch_settings/start_checks.js
|
||||
function startChecks(
|
||||
checkers: SettingsChecker[],
|
||||
http: { fetch: any },
|
||||
updateModel: (properties: any) => void
|
||||
) {
|
||||
const runCheck = async (currentChecker: SettingsChecker): Promise<any> => {
|
||||
updateModel({ checkMessage: currentChecker.message });
|
||||
|
||||
const { found, reason, error, errorReason } = await executeCheck(currentChecker, http);
|
||||
|
||||
if (error) {
|
||||
updateModel({ errors: errorReason });
|
||||
if (currentChecker.next) {
|
||||
return runCheck(currentChecker.next);
|
||||
}
|
||||
} else if (found) {
|
||||
return updateModel({
|
||||
reason,
|
||||
isLoading: false,
|
||||
checkMessage: null,
|
||||
});
|
||||
} else if (currentChecker.next) {
|
||||
return runCheck(currentChecker.next);
|
||||
}
|
||||
|
||||
// dead end
|
||||
updateModel({
|
||||
reason: null,
|
||||
isLoading: false,
|
||||
checkMessage: null,
|
||||
});
|
||||
};
|
||||
|
||||
const _checkers = mapCheckers(checkers);
|
||||
return runCheck(_checkers[0]);
|
||||
}
|
||||
|
||||
async function executeCheck(checker: SettingsChecker, http: { fetch: any }): Promise<any> {
|
||||
try {
|
||||
const response = await http.fetch(checker.api, {
|
||||
method: 'GET',
|
||||
});
|
||||
const { found, reason } = response;
|
||||
|
||||
return { found, reason };
|
||||
} catch (err: any) {
|
||||
const { data } = err;
|
||||
|
||||
return {
|
||||
error: true,
|
||||
found: false,
|
||||
errorReason: data,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function formatClusters(clusters: any): any[] {
|
||||
return clusters.map(formatCluster);
|
||||
}
|
||||
|
||||
function formatCluster(cluster: any) {
|
||||
if (cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID) {
|
||||
cluster.cluster_name = 'Standalone Cluster';
|
||||
}
|
||||
return cluster;
|
||||
}
|
|
@ -32,9 +32,9 @@ import { CloudDeployment } from './blurbs';
|
|||
import { getSafeForExternalLink } from '../../lib/get_safe_for_external_link';
|
||||
|
||||
function NoDataMessage(props) {
|
||||
const { isLoading, reason, checkMessage } = props;
|
||||
const { isLoading, reason, checkMessage, isCollectionEnabledUpdated } = props;
|
||||
|
||||
if (isLoading) {
|
||||
if ((isCollectionEnabledUpdated && !reason) || isLoading) {
|
||||
return <CheckingSettings checkMessage={checkMessage} />;
|
||||
}
|
||||
|
||||
|
|
|
@ -159,6 +159,8 @@ export const disableElasticsearchInternalCollection = async () => {
|
|||
};
|
||||
|
||||
export const toggleSetupMode = (inSetupMode: boolean) => {
|
||||
if (isReactMigrationEnabled()) return setupModeReact.toggleSetupMode(inSetupMode);
|
||||
|
||||
checkAngularState();
|
||||
|
||||
const globalState = angularState.injector.get('globalState');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue