[Monitoring] Migrate Elasticsearch ML Jobs View from Angular (#113974)
* [Monitoring] Migrate Elasticsearch ML Jobs View from Angular * Add types * Fix broken node links
This commit is contained in:
parent
f030960c98
commit
cc84798f10
|
@ -39,6 +39,7 @@ import { ElasticsearchIndicesPage } from './pages/elasticsearch/indices_page';
|
|||
import { ElasticsearchIndexPage } from './pages/elasticsearch/index_page';
|
||||
import { ElasticsearchIndexAdvancedPage } from './pages/elasticsearch/index_advanced_page';
|
||||
import { ElasticsearchNodePage } from './pages/elasticsearch/node_page';
|
||||
import { ElasticsearchMLJobsPage } from './pages/elasticsearch/ml_jobs_page';
|
||||
import { ElasticsearchNodeAdvancedPage } from './pages/elasticsearch/node_advanced_page';
|
||||
import { ElasticsearchCcrPage } from './pages/elasticsearch/ccr_page';
|
||||
import { ElasticsearchCcrShardPage } from './pages/elasticsearch/ccr_shard_page';
|
||||
|
@ -108,6 +109,13 @@ const MonitoringApp: React.FC<{
|
|||
/>
|
||||
|
||||
{/* ElasticSearch Views */}
|
||||
<RouteInit
|
||||
path="/elasticsearch/ml_jobs"
|
||||
component={ElasticsearchMLJobsPage}
|
||||
codePaths={[CODE_PATH_ELASTICSEARCH]}
|
||||
fetchAllClusters={false}
|
||||
/>
|
||||
|
||||
<RouteInit
|
||||
path="/elasticsearch/ccr/:index/shard/:shardId"
|
||||
component={ElasticsearchCcrShardPage}
|
||||
|
|
|
@ -22,6 +22,7 @@ export const ElasticsearchIndicesPage: React.FC<ComponentProps> = ({ clusters })
|
|||
const { services } = useKibana<{ data: any }>();
|
||||
const { getPaginationTableProps } = useTable('elasticsearch.indices');
|
||||
const clusterUuid = globalState.cluster_uuid;
|
||||
const ccs = globalState.ccs;
|
||||
const cluster = find(clusters, {
|
||||
cluster_uuid: clusterUuid,
|
||||
});
|
||||
|
@ -53,6 +54,7 @@ export const ElasticsearchIndicesPage: React.FC<ComponentProps> = ({ clusters })
|
|||
show_system_indices: showSystemIndices,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ccs,
|
||||
timeRange: {
|
||||
min: bounds.min.toISOString(),
|
||||
max: bounds.max.toISOString(),
|
||||
|
@ -60,7 +62,13 @@ export const ElasticsearchIndicesPage: React.FC<ComponentProps> = ({ clusters })
|
|||
}),
|
||||
});
|
||||
setData(response);
|
||||
}, [showSystemIndices, clusterUuid, services.data?.query.timefilter.timefilter, services.http]);
|
||||
}, [
|
||||
ccs,
|
||||
showSystemIndices,
|
||||
clusterUuid,
|
||||
services.data?.query.timefilter.timefilter,
|
||||
services.http,
|
||||
]);
|
||||
|
||||
return (
|
||||
<ElasticsearchTemplate
|
||||
|
@ -70,7 +78,7 @@ export const ElasticsearchIndicesPage: React.FC<ComponentProps> = ({ clusters })
|
|||
data-test-subj="elasticsearchOverviewPage"
|
||||
cluster={cluster}
|
||||
>
|
||||
<div data-test-subj="elasticsearchNodesListingPage">
|
||||
<div data-test-subj="elasticsearchIndicesListingPage">
|
||||
<SetupModeRenderer
|
||||
render={({ flyoutComponent, bottomBarComponent }: SetupModeProps) => (
|
||||
<SetupModeContext.Provider value={{ setupModeSupported: true }}>
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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, { useContext, useState, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { find } from 'lodash';
|
||||
import { ElasticsearchTemplate } from './elasticsearch_template';
|
||||
import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { GlobalStateContext } from '../../global_state_context';
|
||||
import { ElasticsearchMLJobs } from '../../../components/elasticsearch';
|
||||
import { ComponentProps } from '../../route_init';
|
||||
import { SetupModeRenderer } from '../../setup_mode/setup_mode_renderer';
|
||||
import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context';
|
||||
import { useTable } from '../../hooks/use_table';
|
||||
import type { MLJobs } from '../../../types';
|
||||
|
||||
interface SetupModeProps {
|
||||
setupMode: any;
|
||||
flyoutComponent: any;
|
||||
bottomBarComponent: any;
|
||||
}
|
||||
|
||||
export const ElasticsearchMLJobsPage: React.FC<ComponentProps> = ({ clusters }) => {
|
||||
const globalState = useContext(GlobalStateContext);
|
||||
const { services } = useKibana<{ data: any }>();
|
||||
const { getPaginationTableProps } = useTable('elasticsearch.mlJobs');
|
||||
const clusterUuid = globalState.cluster_uuid;
|
||||
const ccs = globalState.ccs;
|
||||
const cluster = find(clusters, {
|
||||
cluster_uuid: clusterUuid,
|
||||
});
|
||||
const [data, setData] = useState({} as any);
|
||||
|
||||
const title = i18n.translate('xpack.monitoring.elasticsearch.mlJobs.routeTitle', {
|
||||
defaultMessage: 'Elasticsearch - Machine Learning Jobs',
|
||||
});
|
||||
|
||||
const pageTitle = i18n.translate('xpack.monitoring.elasticsearch.mlJobs.pageTitle', {
|
||||
defaultMessage: 'Elasticsearch machine learning jobs',
|
||||
});
|
||||
|
||||
const getPageData = useCallback(async () => {
|
||||
const bounds = services.data?.query.timefilter.timefilter.getBounds();
|
||||
const url = `../api/monitoring/v1/clusters/${clusterUuid}/elasticsearch/ml_jobs`;
|
||||
const response = await services.http?.fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
ccs,
|
||||
timeRange: {
|
||||
min: bounds.min.toISOString(),
|
||||
max: bounds.max.toISOString(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
setData({
|
||||
clusterStatus: response.clusterStatus,
|
||||
jobs: (response.rows as MLJobs).map((job) => {
|
||||
if ('ml' in job && job.ml?.job) {
|
||||
return {
|
||||
...job.ml.job,
|
||||
node: job.node,
|
||||
job_id: job.ml.job.id,
|
||||
};
|
||||
}
|
||||
return job;
|
||||
}),
|
||||
});
|
||||
}, [ccs, clusterUuid, services.data?.query.timefilter.timefilter, services.http]);
|
||||
|
||||
return (
|
||||
<ElasticsearchTemplate
|
||||
title={title}
|
||||
pageTitle={pageTitle}
|
||||
getPageData={getPageData}
|
||||
data-test-subj="elasticsearchOverviewPage"
|
||||
cluster={cluster}
|
||||
>
|
||||
<div data-test-subj="elasticsearchMLJobsListingPage">
|
||||
<SetupModeRenderer
|
||||
render={({ flyoutComponent, bottomBarComponent }: SetupModeProps) => (
|
||||
<SetupModeContext.Provider value={{ setupModeSupported: true }}>
|
||||
{flyoutComponent}
|
||||
<ElasticsearchMLJobs
|
||||
clusterStatus={data.clusterStatus}
|
||||
jobs={data.jobs}
|
||||
{...getPaginationTableProps()}
|
||||
/>
|
||||
{bottomBarComponent}
|
||||
</SetupModeContext.Provider>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</ElasticsearchTemplate>
|
||||
);
|
||||
};
|
8
x-pack/plugins/monitoring/public/components/elasticsearch/cluster_status/index.d.ts
vendored
Normal file
8
x-pack/plugins/monitoring/public/components/elasticsearch/cluster_status/index.d.ts
vendored
Normal file
|
@ -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 ClusterStatus: FunctionComponent<Props>;
|
|
@ -8,4 +8,5 @@
|
|||
export const ElasticsearchOverview: FunctionComponent<Props>;
|
||||
export const ElasticsearchNodes: FunctionComponent<Props>;
|
||||
export const ElasticsearchIndices: FunctionComponent<Props>;
|
||||
export const ElasticsearchMLJobs: FunctionComponent<Props>;
|
||||
export const NodeReact: FunctionComponent<Props>;
|
||||
|
|
|
@ -9,3 +9,4 @@ export { ElasticsearchOverview } from './overview';
|
|||
export { ElasticsearchNodes } from './nodes';
|
||||
export { NodeReact } from './node';
|
||||
export { ElasticsearchIndices } from './indices';
|
||||
export { ElasticsearchMLJobs } from './ml_jobs';
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { StatusIcon } from '../../status_icon';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { StatusIcon } from '../../status_icon';
|
||||
|
||||
export function MachineLearningJobStatusIcon({ status }) {
|
||||
export function MachineLearningJobStatusIcon({ status }: { status: string }) {
|
||||
const type = (() => {
|
||||
const statusKey = status.toUpperCase();
|
||||
|
|
@ -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 { ElasticsearchMLJobs } from './ml_jobs';
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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 { capitalize } from 'lodash';
|
||||
import numeral from '@elastic/numeral';
|
||||
import React from 'react';
|
||||
import {
|
||||
EuiLink,
|
||||
EuiPage,
|
||||
EuiPageContent,
|
||||
EuiPageBody,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
Pagination,
|
||||
EuiTableSortingType,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { LARGE_ABBREVIATED, LARGE_BYTES } from '../../../../common/formatting';
|
||||
import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link';
|
||||
import type { MLJobs } from '../../../types';
|
||||
import { EuiMonitoringTable } from '../../table';
|
||||
import { MachineLearningJobStatusIcon } from '../ml_job_listing/status_icon';
|
||||
import { ClusterStatus } from '../cluster_status';
|
||||
|
||||
interface Props {
|
||||
clusterStatus: boolean;
|
||||
jobs: MLJobs;
|
||||
onTableChange: () => void;
|
||||
sorting: EuiTableSortingType<string>;
|
||||
pagination: Pagination;
|
||||
}
|
||||
|
||||
type MLJob = MLJobs[0];
|
||||
|
||||
export const ElasticsearchMLJobs = ({
|
||||
clusterStatus,
|
||||
jobs,
|
||||
sorting,
|
||||
pagination,
|
||||
onTableChange,
|
||||
}: Props) => {
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
<EuiPanel>
|
||||
<ClusterStatus stats={clusterStatus} />
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiPageContent>
|
||||
<EuiMonitoringTable
|
||||
className="mlJobsTable"
|
||||
rows={jobs}
|
||||
columns={columns}
|
||||
sorting={{
|
||||
...sorting,
|
||||
sort: {
|
||||
...sorting.sort,
|
||||
field: 'job_id',
|
||||
},
|
||||
}}
|
||||
pagination={pagination}
|
||||
message={i18n.translate(
|
||||
'xpack.monitoring.elasticsearch.mlJobListing.noJobsDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'There are no Machine Learning Jobs that match your query. Try changing the time range selection.',
|
||||
}
|
||||
)}
|
||||
search={{
|
||||
box: {
|
||||
incremental: true,
|
||||
placeholder: i18n.translate(
|
||||
'xpack.monitoring.elasticsearch.mlJobListing.filterJobsPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Filter Jobs…',
|
||||
}
|
||||
),
|
||||
},
|
||||
}}
|
||||
onTableChange={onTableChange}
|
||||
executeQueryOptions={{
|
||||
defaultFields: ['job_id'],
|
||||
}}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.jobIdTitle', {
|
||||
defaultMessage: 'Job ID',
|
||||
}),
|
||||
field: 'job_id',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.stateTitle', {
|
||||
defaultMessage: 'State',
|
||||
}),
|
||||
field: 'state',
|
||||
sortable: true,
|
||||
render: (state: string) => (
|
||||
<div>
|
||||
<MachineLearningJobStatusIcon status={state} />
|
||||
|
||||
{capitalize(state)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.processedRecordsTitle', {
|
||||
defaultMessage: 'Processed Records',
|
||||
}),
|
||||
field: 'data_counts.processed_record_count',
|
||||
sortable: true,
|
||||
render: (value: unknown) => <span>{numeral(value).format(LARGE_ABBREVIATED)}</span>,
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.modelSizeTitle', {
|
||||
defaultMessage: 'Model Size',
|
||||
}),
|
||||
field: 'model_size_stats.model_bytes',
|
||||
sortable: true,
|
||||
render: (value: unknown) => <span>{numeral(value).format(LARGE_BYTES)}</span>,
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.forecastsTitle', {
|
||||
defaultMessage: 'Forecasts',
|
||||
}),
|
||||
field: 'forecasts_stats.total',
|
||||
sortable: true,
|
||||
render: (value: unknown) => <span>{numeral(value).format(LARGE_ABBREVIATED)}</span>,
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.monitoring.elasticsearch.mlJobListing.nodeTitle', {
|
||||
defaultMessage: 'Node',
|
||||
}),
|
||||
field: 'node.name',
|
||||
sortable: true,
|
||||
render: (name: string, job: MLJob) => {
|
||||
if (job.node) {
|
||||
if ('id' in job.node) {
|
||||
return (
|
||||
<EuiLink href={getSafeForExternalLink(`#/elasticsearch/nodes/${job.node.id}`)}>
|
||||
{name}
|
||||
</EuiLink>
|
||||
);
|
||||
} else return <span>{name}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.monitoring.elasticsearch.mlJobListing.noDataLabel"
|
||||
defaultMessage="N/A"
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
8
x-pack/plugins/monitoring/public/components/status_icon/index.d.ts
vendored
Normal file
8
x-pack/plugins/monitoring/public/components/status_icon/index.d.ts
vendored
Normal file
|
@ -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 StatusIcon: FunctionComponent<Props>;
|
|
@ -7,3 +7,4 @@
|
|||
|
||||
export const euiTableStorageGetter: (string) => any;
|
||||
export const euiTableStorageSetter: (string) => any;
|
||||
export const EuiMonitoringTable: FunctionComponent<Props>;
|
||||
|
|
|
@ -14,6 +14,8 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/p
|
|||
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
export { MonitoringConfig } from '../server';
|
||||
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
|
||||
export { MLJobs } from '../server/lib/elasticsearch/get_ml_jobs';
|
||||
|
||||
export interface MonitoringStartPluginDependencies {
|
||||
navigation: NavigationStart;
|
||||
|
|
|
@ -35,6 +35,8 @@ export function handleResponse(response: ElasticsearchResponse) {
|
|||
);
|
||||
}
|
||||
|
||||
export type MLJobs = ReturnType<typeof handleResponse>;
|
||||
|
||||
export function getMlJobs(req: LegacyRequest, esIndexPattern: string) {
|
||||
checkParam(esIndexPattern, 'esIndexPattern in getMlJobs');
|
||||
|
||||
|
|
Loading…
Reference in a new issue