From 3d068c5db947799b958cef3bc190d3b102aca0b9 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Tue, 9 Feb 2021 15:03:23 +0000 Subject: [PATCH] [ML] Lazy ml node UI improvements (#90455) * [ML] Lazy ml node UI improvements * fixing test * adding awaitingMlNodeAllocation to default datafeed response * changing datafeed icon when node is not assigned * updating text Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/ml/common/types/modules.ts | 1 + .../jobs_awaiting_node_warning.tsx | 8 +++--- .../new_job_awaiting_node.tsx | 9 ++++-- .../new_job/recognize/components/job_item.tsx | 17 +++++++++-- .../jobs/new_job/recognize/page.tsx | 20 +++++++++++-- .../ml_nodes_check/check_ml_nodes.ts | 8 ++++++ .../application/ml_nodes_check/index.ts | 1 + .../forecasting_modal/run_controls.js | 4 +-- .../models/data_recognizer/data_recognizer.ts | 28 ++++++++++++++++--- .../apis/ml/modules/setup_module.ts | 1 + 10 files changed, 79 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/ml/common/types/modules.ts b/x-pack/plugins/ml/common/types/modules.ts index faa9c700f95a4..7c9623d3e68ec 100644 --- a/x-pack/plugins/ml/common/types/modules.ts +++ b/x-pack/plugins/ml/common/types/modules.ts @@ -68,6 +68,7 @@ export interface KibanaObjectResponse extends ResultItem { export interface DatafeedResponse extends ResultItem { started: boolean; + awaitingMlNodeAllocation?: boolean; error?: ErrorType; } diff --git a/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/jobs_awaiting_node_warning.tsx b/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/jobs_awaiting_node_warning.tsx index bc216ce62a57c..2cc36b7a2adf7 100644 --- a/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/jobs_awaiting_node_warning.tsx +++ b/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/jobs_awaiting_node_warning.tsx @@ -9,14 +9,14 @@ import React, { Fragment, FC } from 'react'; import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { isCloud } from '../../services/ml_server_info'; +import { lazyMlNodesAvailable } from '../../ml_nodes_check'; interface Props { jobCount: number; } export const JobsAwaitingNodeWarning: FC = ({ jobCount }) => { - if (isCloud() === false || jobCount === 0) { + if (lazyMlNodesAvailable() === false || jobCount === 0) { return null; } @@ -26,7 +26,7 @@ export const JobsAwaitingNodeWarning: FC = ({ jobCount }) => { title={ } color="primary" @@ -35,7 +35,7 @@ export const JobsAwaitingNodeWarning: FC = ({ jobCount }) => {
= () => { + if (lazyMlNodesAvailable() === false) { + return null; + } + return ( } color="primary" @@ -31,7 +36,7 @@ export const NewJobAwaitingNodeWarning: FC = () => {
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx index 760ff67d97b9d..311e291cf2519 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx @@ -21,7 +21,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ModuleJobUI } from '../page'; import { SETUP_RESULTS_WIDTH } from './module_jobs'; import { tabColor } from '../../../../../../common/util/group_color_utils'; -import { JobOverride } from '../../../../../../common/types/modules'; +import { JobOverride, DatafeedResponse } from '../../../../../../common/types/modules'; import { extractErrorMessage } from '../../../../../../common/util/errors'; interface JobItemProps { @@ -151,8 +151,8 @@ export const JobItem: FC = memo( = memo( ); } ); + +function getDatafeedStartedIcon({ + awaitingMlNodeAllocation, + success, +}: DatafeedResponse): { type: string; color: string } { + if (awaitingMlNodeAllocation === true) { + return { type: 'alert', color: 'warning' }; + } + + return success ? { type: 'check', color: 'secondary' } : { type: 'cross', color: 'danger' }; +} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx index 14018d485e04c..271898654ca49 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx @@ -43,6 +43,7 @@ import { TimeRange } from '../common/components'; import { JobId } from '../../../../../common/types/anomaly_detection_jobs'; import { ML_PAGES } from '../../../../../common/constants/ml_url_generator'; import { TIME_FORMAT } from '../../../../../common/constants/time_format'; +import { JobsAwaitingNodeWarning } from '../../../components/jobs_awaiting_node_warning'; export interface ModuleJobUI extends ModuleJob { datafeedResult?: DatafeedResponse; @@ -84,6 +85,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { const [saveState, setSaveState] = useState(SAVE_STATE.NOT_SAVED); const [resultsUrl, setResultsUrl] = useState(''); const [existingGroups, setExistingGroups] = useState(existingGroupIds); + const [jobsAwaitingNodeCount, setJobsAwaitingNodeCount] = useState(0); // #endregion const { @@ -204,9 +206,19 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { }); setResultsUrl(url); - const failedJobsCount = jobsResponse.reduce((count, { success }) => { - return success ? count : count + 1; - }, 0); + const failedJobsCount = jobsResponse.reduce( + (count, { success }) => (success ? count : count + 1), + 0 + ); + + const lazyJobsCount = datafeedsResponse.reduce( + (count, { awaitingMlNodeAllocation }) => + awaitingMlNodeAllocation === true ? count + 1 : count, + 0 + ); + + setJobsAwaitingNodeCount(lazyJobsCount); + setSaveState( failedJobsCount === 0 ? SAVE_STATE.SAVED @@ -291,6 +303,8 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { )} + {jobsAwaitingNodeCount > 0 && } + diff --git a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts index 71aef2da312a6..551a5823c1f41 100644 --- a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts +++ b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts @@ -48,6 +48,14 @@ export function mlNodesAvailable() { return mlNodeCount !== 0 || lazyMlNodeCount !== 0; } +export function currentMlNodesAvailable() { + return mlNodeCount !== 0; +} + +export function lazyMlNodesAvailable() { + return lazyMlNodeCount !== 0; +} + export function permissionToViewMlNodeCount() { return userHasPermissionToViewMlNodeCount; } diff --git a/x-pack/plugins/ml/public/application/ml_nodes_check/index.ts b/x-pack/plugins/ml/public/application/ml_nodes_check/index.ts index 295ff1aca2ec7..8102f95c035b0 100644 --- a/x-pack/plugins/ml/public/application/ml_nodes_check/index.ts +++ b/x-pack/plugins/ml/public/application/ml_nodes_check/index.ts @@ -9,5 +9,6 @@ export { checkMlNodesAvailable, getMlNodeCount, mlNodesAvailable, + lazyMlNodesAvailable, permissionToViewMlNodeCount, } from './check_ml_nodes'; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/run_controls.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/run_controls.js index a37ad5fd30517..b36acba8b4ba4 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/run_controls.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/run_controls.js @@ -27,7 +27,7 @@ import { import { JOB_STATE } from '../../../../../common/constants/states'; import { FORECAST_DURATION_MAX_DAYS } from './forecasting_modal'; import { ForecastProgress } from './forecast_progress'; -import { mlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes'; +import { currentMlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes'; import { checkPermission, createPermissionFailureMessage, @@ -41,7 +41,7 @@ function getRunInputDisabledState(job, isForecastRequested) { // - No canForecastJob permission // - Job is not in an OPENED or CLOSED state // - A new forecast has been requested - if (mlNodesAvailable() === false) { + if (currentMlNodesAvailable() === false) { return { isDisabled: true, isDisabledToolTipText: i18n.translate( diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 92dfe3aa0fbf9..a1fac92d45b4e 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -491,6 +491,7 @@ export class DataRecognizer { const startedDatafeed = startResults[df.id]; if (startedDatafeed !== undefined) { df.started = startedDatafeed.started; + df.awaitingMlNodeAllocation = startedDatafeed.awaitingMlNodeAllocation; if (startedDatafeed.error !== undefined) { df.error = startedDatafeed.error; } @@ -749,9 +750,20 @@ export class DataRecognizer { datafeeds.map(async (datafeed) => { try { await this.saveDatafeed(datafeed); - return { id: datafeed.id, success: true, started: false }; + return { + id: datafeed.id, + success: true, + started: false, + awaitingMlNodeAllocation: false, + }; } catch ({ body }) { - return { id: datafeed.id, success: false, started: false, error: body }; + return { + id: datafeed.id, + success: false, + started: false, + awaitingMlNodeAllocation: false, + error: body, + }; } }) ); @@ -811,11 +823,18 @@ export class DataRecognizer { duration.end = (end as unknown) as string; } - await this._mlClient.startDatafeed({ + const { + body: { started, node }, + } = await this._mlClient.startDatafeed<{ + started: boolean; + node: string; + }>({ datafeed_id: datafeed.id, ...duration, }); - result.started = true; + + result.started = started; + result.awaitingMlNodeAllocation = node?.length === 0; } catch ({ body }) { result.started = false; result.error = body; @@ -845,6 +864,7 @@ export class DataRecognizer { if (d.id === d2.id) { d.success = d2.success; d.started = d2.started; + d.awaitingMlNodeAllocation = d2.awaitingMlNodeAllocation; if (d2.error !== undefined) { d.error = d2.error; } diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index be1ac7fbb0965..8064d498774a3 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -772,6 +772,7 @@ export default ({ getService }: FtrProviderContext) => { const expectedRspDatafeeds = sortBy( testData.expected.jobs.map((job) => { return { + awaitingMlNodeAllocation: false, id: `datafeed-${job.jobId}`, success: true, started: testData.requestBody.startDatafeed,