From 2cca084915e7bc4388af2617e5628aeba862f2e8 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 6 Jul 2018 18:36:41 +0100 Subject: [PATCH 1/7] [ML] Adding jobs stats bar to top of jobs list --- .../jobs_list_view/jobs_list_view.js | 8 +- .../components/jobs_stats_bar/index.js | 8 ++ .../jobs_stats_bar/jobs_stats_bar.js | 116 ++++++++++++++++++ .../jobs_stats_bar/styles/main.less | 13 ++ .../node_available_warning/index.js | 8 ++ .../node_available_warning.js | 44 +++++++ .../ml/public/jobs/jobs_list_new/jobs.js | 63 ++++------ .../ml/server/models/job_service/jobs.js | 1 + 8 files changed, 219 insertions(+), 42 deletions(-) create mode 100644 x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/index.js create mode 100644 x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js create mode 100644 x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/styles/main.less create mode 100644 x-pack/plugins/ml/public/jobs/jobs_list_new/components/node_available_warning/index.js create mode 100644 x-pack/plugins/ml/public/jobs/jobs_list_new/components/node_available_warning/node_available_warning.js diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js index 0cd1d33b4562c..a138e426b3b0a 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js @@ -35,7 +35,8 @@ export class JobsListView extends Component { fullJobsList: {}, selectedJobs: [], itemIdToExpandedRowMap: {}, - filterClauses: [] + filterClauses: [], + updateJobStats: props.updateJobStats, }; this.updateFunctions = {}; @@ -59,6 +60,10 @@ export class JobsListView extends Component { this.clearRefreshInterval(); } + static getDerivedStateFromProps(props) { + return { updateJobStats: props.updateJobStats }; + } + initAutoRefresh() { const { value, pause } = timefilter.getRefreshInterval(); if (pause === false && value === 0) { @@ -227,6 +232,7 @@ export class JobsListView extends Component { const filteredJobsSummaryList = filterJobs(jobsSummaryList, this.state.filterClauses); this.setState({ jobsSummaryList, filteredJobsSummaryList, fullJobsList }, () => { this.refreshSelectedJobs(); + this.state.updateJobStats(jobsSummaryList); }); Object.keys(this.updateFunctions).forEach((j) => { diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/index.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/index.js new file mode 100644 index 0000000000000..bc12f3b2ad655 --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/index.js @@ -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'; diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js new file mode 100644 index 0000000000000..e05f51e0fc74e --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js @@ -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; + * 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.node !== undefined) { + mlNodes[job.node] = {}; + } + }); + + 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 ( + + {stat.label}: {stat.value} + + ); +} +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); + } + + render() { + const { jobStats } = this.state; + const stats = Object.keys(jobStats).map(k => jobStats[k]); + + return ( +
+ { + stats.filter(s => (s.show)).map(s => ) + } +
+ ); + } +} +JobStatsBar.propTypes = { + setUpdateJobStats: PropTypes.func.isRequired, +}; + diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/styles/main.less b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/styles/main.less new file mode 100644 index 0000000000000..e5c46f2739465 --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/styles/main.less @@ -0,0 +1,13 @@ +.jobs-stats-bar-new { + + padding: 14px; + background-color: #EFF0F1; + + .stat { + margin-right: 10px; + .stat-label {} + .stat-value { + font-weight: bold + } + } +} diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/node_available_warning/index.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/node_available_warning/index.js new file mode 100644 index 0000000000000..3e3ca96a4d4c3 --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/node_available_warning/index.js @@ -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'; diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/node_available_warning/node_available_warning.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/node_available_warning/node_available_warning.js new file mode 100644 index 0000000000000..7e0d39ffd4c07 --- /dev/null +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/node_available_warning/node_available_warning.js @@ -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 (); + } else { + return ( + + +

+ There are no ML nodes available.
+ You will not be able to create or run jobs. + {isCloud && + +  This can be configured in Cloud here. + + } +

+
+ +
+ ); + } +} diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/jobs.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/jobs.js index 4f47e177d4b4a..9b3ea3c77d98d 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/jobs.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/jobs.js @@ -8,69 +8,50 @@ 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 (); - } else { - return ( - - -

- There are no ML nodes available.
- You will not be able to create or run jobs. - {isCloud && - -  This can be configured in Cloud here. - - } -

-
- -
- ); - } -} export class JobsPage extends Component { constructor(props) { super(props); this.state = { + jobsSummaryList: [], + updateJobStats: () => {}, }; } + setUpdateJobStats = (updateJobStats) => { + this.setState({ updateJobStats }); + } + render() { return ( -
- -
-
- -
-
+ + +
+ +
+
+ +
+
-
+
- + - -
+ +
+ ); } } diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.js b/x-pack/plugins/ml/server/models/job_service/jobs.js index d03c9e934d804..424a59d3f8cd2 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.js +++ b/x-pack/plugins/ml/server/models/job_service/jobs.js @@ -73,6 +73,7 @@ export function jobsProvider(callWithRequest) { datafeedState: (hasDatafeed && job.datafeed_config.state) ? job.datafeed_config.state : '', latestTimeStamp, earliestTimeStamp, + node: (hasDatafeed && job.datafeed_config.node) ? job.datafeed_config.node.name : undefined, }; if (jobIds.find(j => (j === tempJob.id))) { tempJob.fullJob = job; From fbb5bc7a4a02f79374415e52bc7b929ad7fef288 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 6 Jul 2018 19:19:09 +0100 Subject: [PATCH 2/7] unsetting update function --- .../components/jobs_stats_bar/jobs_stats_bar.js | 5 +++++ x-pack/plugins/ml/public/jobs/jobs_list_new/jobs.js | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js index e05f51e0fc74e..7c8b6bf645887 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js @@ -97,6 +97,10 @@ export class JobStatsBar extends Component { this.props.setUpdateJobStats(this.updateJobStats); } + componentWillUnmount() { + this.props.unsetUpdateJobStats(); + } + render() { const { jobStats } = this.state; const stats = Object.keys(jobStats).map(k => jobStats[k]); @@ -112,5 +116,6 @@ export class JobStatsBar extends Component { } JobStatsBar.propTypes = { setUpdateJobStats: PropTypes.func.isRequired, + unsetUpdateJobStats: PropTypes.func.isRequired, }; diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/jobs.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/jobs.js index 9b3ea3c77d98d..7e3c8516030b2 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/jobs.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/jobs.js @@ -33,10 +33,17 @@ export class JobsPage extends Component { this.setState({ updateJobStats }); } + unsetUpdateJobStats = () => { + this.setUpdateJobStats(() => {}); + } + render() { return ( - +
From e6a986a60dd7d130fd36e38cf9c41174a0906ead Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 6 Jul 2018 19:27:24 +0100 Subject: [PATCH 3/7] changing node variable name --- .../jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js | 4 ++-- x-pack/plugins/ml/server/models/job_service/jobs.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js index 7c8b6bf645887..fc4f6db0469cf 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/jobs_stats_bar.js @@ -45,8 +45,8 @@ function createJobStats(jobsSummaryList) { jobStats.activeDatafeeds.value++; } - if (job.node !== undefined) { - mlNodes[job.node] = {}; + if (job.nodeName !== undefined) { + mlNodes[job.nodeName] = {}; } }); diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.js b/x-pack/plugins/ml/server/models/job_service/jobs.js index 424a59d3f8cd2..313b685ac10ad 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.js +++ b/x-pack/plugins/ml/server/models/job_service/jobs.js @@ -73,7 +73,7 @@ export function jobsProvider(callWithRequest) { datafeedState: (hasDatafeed && job.datafeed_config.state) ? job.datafeed_config.state : '', latestTimeStamp, earliestTimeStamp, - node: (hasDatafeed && job.datafeed_config.node) ? job.datafeed_config.node.name : undefined, + nodeName: (job.node) ? job.node.name : undefined, }; if (jobIds.find(j => (j === tempJob.id))) { tempJob.fullJob = job; From c7f3291620bf121aadbbfa024822fe717998e2c6 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 6 Jul 2018 22:56:21 +0100 Subject: [PATCH 4/7] small refactor --- .../jobs_list_new/components/jobs_list_view/jobs_list_view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js index a138e426b3b0a..f8bf3d4940df1 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js @@ -60,8 +60,8 @@ export class JobsListView extends Component { this.clearRefreshInterval(); } - static getDerivedStateFromProps(props) { - return { updateJobStats: props.updateJobStats }; + static getDerivedStateFromProps({ updateJobStats }) { + return { updateJobStats }; } initAutoRefresh() { From b2015687e87730afe37381343a20c8b35e030feb Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 9 Jul 2018 10:14:29 +0100 Subject: [PATCH 5/7] using props copy of `updateJobStats` --- .../components/jobs_list_view/jobs_list_view.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js index f8bf3d4940df1..9c7ec6f009c83 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js @@ -36,7 +36,6 @@ export class JobsListView extends Component { selectedJobs: [], itemIdToExpandedRowMap: {}, filterClauses: [], - updateJobStats: props.updateJobStats, }; this.updateFunctions = {}; @@ -60,10 +59,6 @@ export class JobsListView extends Component { this.clearRefreshInterval(); } - static getDerivedStateFromProps({ updateJobStats }) { - return { updateJobStats }; - } - initAutoRefresh() { const { value, pause } = timefilter.getRefreshInterval(); if (pause === false && value === 0) { @@ -232,7 +227,7 @@ export class JobsListView extends Component { const filteredJobsSummaryList = filterJobs(jobsSummaryList, this.state.filterClauses); this.setState({ jobsSummaryList, filteredJobsSummaryList, fullJobsList }, () => { this.refreshSelectedJobs(); - this.state.updateJobStats(jobsSummaryList); + this.props.updateJobStats(jobsSummaryList); }); Object.keys(this.updateFunctions).forEach((j) => { From 784d6e032f1e7ca8357b19976ab5e5ad29b6740e Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 9 Jul 2018 10:18:48 +0100 Subject: [PATCH 6/7] adding proptypes --- .../jobs_list_new/components/jobs_list_view/jobs_list_view.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js index 9c7ec6f009c83..0024387667e68 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_list_view/jobs_list_view.js @@ -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'; @@ -282,3 +283,6 @@ export class JobsListView extends Component { ); } } +JobsListView.propTypes = { + updateJobStats: PropTypes.func.isRequired, +}; From 3d4c0aeca9502b54e806a32a2bbecc6cef76779f Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 9 Jul 2018 10:59:51 +0100 Subject: [PATCH 7/7] adding fixed height --- .../jobs_list_new/components/jobs_stats_bar/styles/main.less | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/styles/main.less b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/styles/main.less index e5c46f2739465..86b6bb7e5d5f5 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/styles/main.less +++ b/x-pack/plugins/ml/public/jobs/jobs_list_new/components/jobs_stats_bar/styles/main.less @@ -1,5 +1,6 @@ .jobs-stats-bar-new { + height: 42px; padding: 14px; background-color: #EFF0F1;