Skip to content

Commit 53b8f45

Browse files
[ML] Lazy ml node UI improvements (#90455) (#90787)
* [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 <[email protected]> Co-authored-by: Kibana Machine <[email protected]>
1 parent 7879757 commit 53b8f45

File tree

10 files changed

+79
-18
lines changed

10 files changed

+79
-18
lines changed

x-pack/plugins/ml/common/types/modules.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export interface KibanaObjectResponse extends ResultItem {
6868

6969
export interface DatafeedResponse extends ResultItem {
7070
started: boolean;
71+
awaitingMlNodeAllocation?: boolean;
7172
error?: ErrorType;
7273
}
7374

x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/jobs_awaiting_node_warning.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import React, { Fragment, FC } from 'react';
99

1010
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
1111
import { FormattedMessage } from '@kbn/i18n/react';
12-
import { isCloud } from '../../services/ml_server_info';
12+
import { lazyMlNodesAvailable } from '../../ml_nodes_check';
1313

1414
interface Props {
1515
jobCount: number;
1616
}
1717

1818
export const JobsAwaitingNodeWarning: FC<Props> = ({ jobCount }) => {
19-
if (isCloud() === false || jobCount === 0) {
19+
if (lazyMlNodesAvailable() === false || jobCount === 0) {
2020
return null;
2121
}
2222

@@ -26,7 +26,7 @@ export const JobsAwaitingNodeWarning: FC<Props> = ({ jobCount }) => {
2626
title={
2727
<FormattedMessage
2828
id="xpack.ml.jobsAwaitingNodeWarning.title"
29-
defaultMessage="Awaiting ML node provisioning"
29+
defaultMessage="Awaiting machine learning node"
3030
/>
3131
}
3232
color="primary"
@@ -35,7 +35,7 @@ export const JobsAwaitingNodeWarning: FC<Props> = ({ jobCount }) => {
3535
<div>
3636
<FormattedMessage
3737
id="xpack.ml.jobsAwaitingNodeWarning.noMLNodesAvailableDescription"
38-
defaultMessage="There {jobCount, plural, one {is} other {are}} {jobCount, plural, one {# job} other {# jobs}} waiting to be started while ML nodes are being provisioned."
38+
defaultMessage="There {jobCount, plural, one {is} other {are}} {jobCount, plural, one {# job} other {# jobs}} waiting for machine learning nodes to start."
3939
values={{
4040
jobCount,
4141
}}

x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/new_job_awaiting_node.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,24 @@ import React, { Fragment, FC } from 'react';
1010
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
1111
import { FormattedMessage } from '@kbn/i18n/react';
1212
import { JobType } from '../../../../common/types/saved_objects';
13+
import { lazyMlNodesAvailable } from '../../ml_nodes_check';
1314

1415
interface Props {
1516
jobType: JobType;
1617
}
1718

1819
export const NewJobAwaitingNodeWarning: FC<Props> = () => {
20+
if (lazyMlNodesAvailable() === false) {
21+
return null;
22+
}
23+
1924
return (
2025
<Fragment>
2126
<EuiCallOut
2227
title={
2328
<FormattedMessage
2429
id="xpack.ml.jobsAwaitingNodeWarning.title"
25-
defaultMessage="Awaiting ML node provisioning"
30+
defaultMessage="Awaiting machine learning node"
2631
/>
2732
}
2833
color="primary"
@@ -31,7 +36,7 @@ export const NewJobAwaitingNodeWarning: FC<Props> = () => {
3136
<div>
3237
<FormattedMessage
3338
id="xpack.ml.newJobAwaitingNodeWarning.noMLNodesAvailableDescription"
34-
defaultMessage="Job cannot be started straight away, an ML node needs to be started. This will happen automatically."
39+
defaultMessage="There are currently no nodes that can run the job, therefore it will remain in OPENING state until an appropriate node becomes available."
3540
/>
3641
</div>
3742
</EuiCallOut>

x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_item.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
2121
import { ModuleJobUI } from '../page';
2222
import { SETUP_RESULTS_WIDTH } from './module_jobs';
2323
import { tabColor } from '../../../../../../common/util/group_color_utils';
24-
import { JobOverride } from '../../../../../../common/types/modules';
24+
import { JobOverride, DatafeedResponse } from '../../../../../../common/types/modules';
2525
import { extractErrorMessage } from '../../../../../../common/util/errors';
2626

2727
interface JobItemProps {
@@ -151,8 +151,8 @@ export const JobItem: FC<JobItemProps> = memo(
151151

152152
<EuiFlexItem grow={false}>
153153
<EuiIcon
154-
type={datafeedResult.started ? 'check' : 'cross'}
155-
color={datafeedResult.started ? 'secondary' : 'danger'}
154+
type={getDatafeedStartedIcon(datafeedResult).type}
155+
color={getDatafeedStartedIcon(datafeedResult).color}
156156
size="m"
157157
aria-label={
158158
setupResult.success
@@ -172,3 +172,14 @@ export const JobItem: FC<JobItemProps> = memo(
172172
);
173173
}
174174
);
175+
176+
function getDatafeedStartedIcon({
177+
awaitingMlNodeAllocation,
178+
success,
179+
}: DatafeedResponse): { type: string; color: string } {
180+
if (awaitingMlNodeAllocation === true) {
181+
return { type: 'alert', color: 'warning' };
182+
}
183+
184+
return success ? { type: 'check', color: 'secondary' } : { type: 'cross', color: 'danger' };
185+
}

x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { TimeRange } from '../common/components';
4343
import { JobId } from '../../../../../common/types/anomaly_detection_jobs';
4444
import { ML_PAGES } from '../../../../../common/constants/ml_url_generator';
4545
import { TIME_FORMAT } from '../../../../../common/constants/time_format';
46+
import { JobsAwaitingNodeWarning } from '../../../components/jobs_awaiting_node_warning';
4647

4748
export interface ModuleJobUI extends ModuleJob {
4849
datafeedResult?: DatafeedResponse;
@@ -84,6 +85,7 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
8485
const [saveState, setSaveState] = useState<SAVE_STATE>(SAVE_STATE.NOT_SAVED);
8586
const [resultsUrl, setResultsUrl] = useState<string>('');
8687
const [existingGroups, setExistingGroups] = useState(existingGroupIds);
88+
const [jobsAwaitingNodeCount, setJobsAwaitingNodeCount] = useState(0);
8789
// #endregion
8890

8991
const {
@@ -204,9 +206,19 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
204206
});
205207

206208
setResultsUrl(url);
207-
const failedJobsCount = jobsResponse.reduce((count, { success }) => {
208-
return success ? count : count + 1;
209-
}, 0);
209+
const failedJobsCount = jobsResponse.reduce(
210+
(count, { success }) => (success ? count : count + 1),
211+
0
212+
);
213+
214+
const lazyJobsCount = datafeedsResponse.reduce(
215+
(count, { awaitingMlNodeAllocation }) =>
216+
awaitingMlNodeAllocation === true ? count + 1 : count,
217+
0
218+
);
219+
220+
setJobsAwaitingNodeCount(lazyJobsCount);
221+
210222
setSaveState(
211223
failedJobsCount === 0
212224
? SAVE_STATE.SAVED
@@ -291,6 +303,8 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
291303
</>
292304
)}
293305

306+
{jobsAwaitingNodeCount > 0 && <JobsAwaitingNodeWarning jobCount={jobsAwaitingNodeCount} />}
307+
294308
<EuiFlexGroup wrap={true} gutterSize="m">
295309
<EuiFlexItem grow={1}>
296310
<EuiPanel grow={false}>

x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ export function mlNodesAvailable() {
4848
return mlNodeCount !== 0 || lazyMlNodeCount !== 0;
4949
}
5050

51+
export function currentMlNodesAvailable() {
52+
return mlNodeCount !== 0;
53+
}
54+
55+
export function lazyMlNodesAvailable() {
56+
return lazyMlNodeCount !== 0;
57+
}
58+
5159
export function permissionToViewMlNodeCount() {
5260
return userHasPermissionToViewMlNodeCount;
5361
}

x-pack/plugins/ml/public/application/ml_nodes_check/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ export {
99
checkMlNodesAvailable,
1010
getMlNodeCount,
1111
mlNodesAvailable,
12+
lazyMlNodesAvailable,
1213
permissionToViewMlNodeCount,
1314
} from './check_ml_nodes';

x-pack/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/run_controls.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
import { JOB_STATE } from '../../../../../common/constants/states';
2828
import { FORECAST_DURATION_MAX_DAYS } from './forecasting_modal';
2929
import { ForecastProgress } from './forecast_progress';
30-
import { mlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
30+
import { currentMlNodesAvailable } from '../../../ml_nodes_check/check_ml_nodes';
3131
import {
3232
checkPermission,
3333
createPermissionFailureMessage,
@@ -41,7 +41,7 @@ function getRunInputDisabledState(job, isForecastRequested) {
4141
// - No canForecastJob permission
4242
// - Job is not in an OPENED or CLOSED state
4343
// - A new forecast has been requested
44-
if (mlNodesAvailable() === false) {
44+
if (currentMlNodesAvailable() === false) {
4545
return {
4646
isDisabled: true,
4747
isDisabledToolTipText: i18n.translate(

x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ export class DataRecognizer {
491491
const startedDatafeed = startResults[df.id];
492492
if (startedDatafeed !== undefined) {
493493
df.started = startedDatafeed.started;
494+
df.awaitingMlNodeAllocation = startedDatafeed.awaitingMlNodeAllocation;
494495
if (startedDatafeed.error !== undefined) {
495496
df.error = startedDatafeed.error;
496497
}
@@ -749,9 +750,20 @@ export class DataRecognizer {
749750
datafeeds.map(async (datafeed) => {
750751
try {
751752
await this.saveDatafeed(datafeed);
752-
return { id: datafeed.id, success: true, started: false };
753+
return {
754+
id: datafeed.id,
755+
success: true,
756+
started: false,
757+
awaitingMlNodeAllocation: false,
758+
};
753759
} catch ({ body }) {
754-
return { id: datafeed.id, success: false, started: false, error: body };
760+
return {
761+
id: datafeed.id,
762+
success: false,
763+
started: false,
764+
awaitingMlNodeAllocation: false,
765+
error: body,
766+
};
755767
}
756768
})
757769
);
@@ -811,11 +823,18 @@ export class DataRecognizer {
811823
duration.end = (end as unknown) as string;
812824
}
813825

814-
await this._mlClient.startDatafeed({
826+
const {
827+
body: { started, node },
828+
} = await this._mlClient.startDatafeed<{
829+
started: boolean;
830+
node: string;
831+
}>({
815832
datafeed_id: datafeed.id,
816833
...duration,
817834
});
818-
result.started = true;
835+
836+
result.started = started;
837+
result.awaitingMlNodeAllocation = node?.length === 0;
819838
} catch ({ body }) {
820839
result.started = false;
821840
result.error = body;
@@ -845,6 +864,7 @@ export class DataRecognizer {
845864
if (d.id === d2.id) {
846865
d.success = d2.success;
847866
d.started = d2.started;
867+
d.awaitingMlNodeAllocation = d2.awaitingMlNodeAllocation;
848868
if (d2.error !== undefined) {
849869
d.error = d2.error;
850870
}

x-pack/test/api_integration/apis/ml/modules/setup_module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,7 @@ export default ({ getService }: FtrProviderContext) => {
772772
const expectedRspDatafeeds = sortBy(
773773
testData.expected.jobs.map((job) => {
774774
return {
775+
awaitingMlNodeAllocation: false,
775776
id: `datafeed-${job.jobId}`,
776777
success: true,
777778
started: testData.requestBody.startDatafeed,

0 commit comments

Comments
 (0)