mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ML] Adding jobs stats bar to top of jobs list (#20527)
* [ML] Adding jobs stats bar to top of jobs list * unsetting update function * changing node variable name * small refactor * using props copy of `updateJobStats` * adding proptypes * adding fixed height
This commit is contained in:
parent
6ef5d8d298
commit
d44840cd19
8 changed files with 231 additions and 42 deletions
|
@ -18,6 +18,7 @@ import { DeleteJobModal } from '../delete_job_modal';
|
|||
import { StartDatafeedModal } from '../start_datafeed_modal';
|
||||
import { MultiJobActions } from '../multi_job_actions';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {
|
||||
Component
|
||||
} from 'react';
|
||||
|
@ -35,7 +36,7 @@ export class JobsListView extends Component {
|
|||
fullJobsList: {},
|
||||
selectedJobs: [],
|
||||
itemIdToExpandedRowMap: {},
|
||||
filterClauses: []
|
||||
filterClauses: [],
|
||||
};
|
||||
|
||||
this.updateFunctions = {};
|
||||
|
@ -227,6 +228,7 @@ export class JobsListView extends Component {
|
|||
const filteredJobsSummaryList = filterJobs(jobsSummaryList, this.state.filterClauses);
|
||||
this.setState({ jobsSummaryList, filteredJobsSummaryList, fullJobsList }, () => {
|
||||
this.refreshSelectedJobs();
|
||||
this.props.updateJobStats(jobsSummaryList);
|
||||
});
|
||||
|
||||
Object.keys(this.updateFunctions).forEach((j) => {
|
||||
|
@ -281,3 +283,6 @@ export class JobsListView extends Component {
|
|||
);
|
||||
}
|
||||
}
|
||||
JobsListView.propTypes = {
|
||||
updateJobStats: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
export { JobStatsBar } from './jobs_stats_bar';
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
import './styles/main.less';
|
||||
import { JOB_STATE, DATAFEED_STATE } from 'plugins/ml/../common/constants/states';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {
|
||||
Component,
|
||||
} from 'react';
|
||||
|
||||
function createJobStats(jobsSummaryList) {
|
||||
|
||||
const jobStats = {
|
||||
activeNodes: { label: 'Active ML Nodes', value: 0, show: true },
|
||||
total: { label: 'Total jobs', value: 0, show: true },
|
||||
open: { label: 'Open jobs', value: 0, show: true },
|
||||
closed: { label: 'Closed jobs', value: 0, show: true },
|
||||
failed: { label: 'Failed jobs', value: 0, show: false },
|
||||
activeDatafeeds: { label: 'Active datafeeds', value: 0, show: true }
|
||||
};
|
||||
|
||||
if (jobsSummaryList === undefined) {
|
||||
return jobStats;
|
||||
}
|
||||
|
||||
// object to keep track of nodes being used by jobs
|
||||
const mlNodes = {};
|
||||
let failedJobs = 0;
|
||||
|
||||
jobsSummaryList.forEach((job) => {
|
||||
if (job.jobState === JOB_STATE.OPENED) {
|
||||
jobStats.open.value++;
|
||||
} else if (job.jobState === JOB_STATE.CLOSED) {
|
||||
jobStats.closed.value++;
|
||||
} else if (job.jobState === JOB_STATE.FAILED) {
|
||||
failedJobs++;
|
||||
}
|
||||
|
||||
if (job.hasDatafeed && job.datafeedState === DATAFEED_STATE.STARTED) {
|
||||
jobStats.activeDatafeeds.value++;
|
||||
}
|
||||
|
||||
if (job.nodeName !== undefined) {
|
||||
mlNodes[job.nodeName] = {};
|
||||
}
|
||||
});
|
||||
|
||||
jobStats.total.value = jobsSummaryList.length;
|
||||
|
||||
// // Only show failed jobs if it is non-zero
|
||||
if (failedJobs) {
|
||||
jobStats.failed.value = failedJobs;
|
||||
jobStats.failed.show = true;
|
||||
} else {
|
||||
jobStats.failed.show = false;
|
||||
}
|
||||
|
||||
jobStats.activeNodes.value = Object.keys(mlNodes).length;
|
||||
|
||||
return jobStats;
|
||||
}
|
||||
|
||||
function Stat({ stat }) {
|
||||
return (
|
||||
<span className="stat">
|
||||
<span className="stat-label">{stat.label}</span>: <span className="stat-value">{stat.value}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Stat.propTypes = {
|
||||
stat: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export class JobStatsBar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
jobsSummaryList: [],
|
||||
jobStats: {},
|
||||
};
|
||||
}
|
||||
|
||||
updateJobStats = (jobsSummaryList) => {
|
||||
const jobStats = createJobStats(jobsSummaryList);
|
||||
this.setState({
|
||||
jobsSummaryList,
|
||||
jobStats,
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.setUpdateJobStats(this.updateJobStats);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.unsetUpdateJobStats();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { jobStats } = this.state;
|
||||
const stats = Object.keys(jobStats).map(k => jobStats[k]);
|
||||
|
||||
return (
|
||||
<div className="jobs-stats-bar-new">
|
||||
{
|
||||
stats.filter(s => (s.show)).map(s => <Stat key={s.label} stat={s} />)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
JobStatsBar.propTypes = {
|
||||
setUpdateJobStats: PropTypes.func.isRequired,
|
||||
unsetUpdateJobStats: PropTypes.func.isRequired,
|
||||
};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
.jobs-stats-bar-new {
|
||||
|
||||
height: 42px;
|
||||
padding: 14px;
|
||||
background-color: #EFF0F1;
|
||||
|
||||
.stat {
|
||||
margin-right: 10px;
|
||||
.stat-label {}
|
||||
.stat-value {
|
||||
font-weight: bold
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
export { NodeAvailableWarning } from './node_available_warning';
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
|
||||
import { mlNodesAvailable, permissionToViewMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
export function NodeAvailableWarning() {
|
||||
const isCloud = false; // placeholder for future specific cloud functionality
|
||||
if ((mlNodesAvailable() === true) || (permissionToViewMlNodeCount() === false)) {
|
||||
return (<span />);
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<EuiCallOut
|
||||
title="No ML nodes available"
|
||||
color="warning"
|
||||
iconType="alert"
|
||||
>
|
||||
<p>
|
||||
There are no ML nodes available.<br />
|
||||
You will not be able to create or run jobs.
|
||||
{isCloud &&
|
||||
<span ng-if="isCloud">
|
||||
This can be configured in Cloud <EuiLink href="#">here</EuiLink>.
|
||||
</span>
|
||||
}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -8,69 +8,57 @@
|
|||
import './styles/main.less';
|
||||
import { NewJobButton } from './components/new_job_button';
|
||||
import { JobsListView } from './components/jobs_list_view';
|
||||
import { mlNodesAvailable, permissionToViewMlNodeCount } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
||||
import { JobStatsBar } from './components/jobs_stats_bar';
|
||||
import { NodeAvailableWarning } from './components/node_available_warning';
|
||||
|
||||
import React, {
|
||||
Component
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
function NodeAvailableWarning() {
|
||||
const isCloud = false; // placeholder for future specific cloud functionality
|
||||
if ((mlNodesAvailable() === true) || (permissionToViewMlNodeCount() === false)) {
|
||||
return (<span />);
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<EuiCallOut
|
||||
title="No ML nodes available"
|
||||
color="warning"
|
||||
iconType="alert"
|
||||
>
|
||||
<p>
|
||||
There are no ML nodes available.<br />
|
||||
You will not be able to create or run jobs.
|
||||
{isCloud &&
|
||||
<span ng-if="isCloud">
|
||||
This can be configured in Cloud <EuiLink href="#">here</EuiLink>.
|
||||
</span>
|
||||
}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class JobsPage extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
jobsSummaryList: [],
|
||||
updateJobStats: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
setUpdateJobStats = (updateJobStats) => {
|
||||
this.setState({ updateJobStats });
|
||||
}
|
||||
|
||||
unsetUpdateJobStats = () => {
|
||||
this.setUpdateJobStats(() => {});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="job-management">
|
||||
<NodeAvailableWarning />
|
||||
<header>
|
||||
<div className="new-job-button-container">
|
||||
<NewJobButton />
|
||||
</div>
|
||||
</header>
|
||||
<React.Fragment>
|
||||
<JobStatsBar
|
||||
setUpdateJobStats={this.setUpdateJobStats}
|
||||
unsetUpdateJobStats={this.unsetUpdateJobStats}
|
||||
/>
|
||||
<div className="job-management">
|
||||
<NodeAvailableWarning />
|
||||
<header>
|
||||
<div className="new-job-button-container">
|
||||
<NewJobButton />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="clear" />
|
||||
<div className="clear" />
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<JobsListView />
|
||||
</div>
|
||||
<JobsListView updateJobStats={this.state.updateJobStats} />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ export function jobsProvider(callWithRequest) {
|
|||
datafeedState: (hasDatafeed && job.datafeed_config.state) ? job.datafeed_config.state : '',
|
||||
latestTimeStamp,
|
||||
earliestTimeStamp,
|
||||
nodeName: (job.node) ? job.node.name : undefined,
|
||||
};
|
||||
if (jobIds.find(j => (j === tempJob.id))) {
|
||||
tempJob.fullJob = job;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue