Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ML] Lazy ml node UI improvements #90455

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/plugins/ml/common/types/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export interface KibanaObjectResponse extends ResultItem {

export interface DatafeedResponse extends ResultItem {
started: boolean;
awaitingMlNodeAllocation?: boolean;
error?: ErrorType;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props> = ({ jobCount }) => {
if (isCloud() === false || jobCount === 0) {
if (lazyMlNodesAvailable() === false || jobCount === 0) {
return null;
}

Expand All @@ -26,7 +26,7 @@ export const JobsAwaitingNodeWarning: FC<Props> = ({ jobCount }) => {
title={
<FormattedMessage
id="xpack.ml.jobsAwaitingNodeWarning.title"
defaultMessage="Awaiting ML node provisioning"
defaultMessage="Awaiting machine learning node"
/>
}
color="primary"
Expand All @@ -35,7 +35,7 @@ export const JobsAwaitingNodeWarning: FC<Props> = ({ jobCount }) => {
<div>
<FormattedMessage
id="xpack.ml.jobsAwaitingNodeWarning.noMLNodesAvailableDescription"
defaultMessage="There {jobCount, plural, one {is} other {are}} {jobCount, plural, one {# job} other {# jobs}} waiting to be started while ML nodes are being provisioned."
defaultMessage="There {jobCount, plural, one {is} other {are}} {jobCount, plural, one {# job} other {# jobs}} waiting for machine learning nodes to start."
values={{
jobCount,
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@ import React, { Fragment, FC } from 'react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { JobType } from '../../../../common/types/saved_objects';
import { lazyMlNodesAvailable } from '../../ml_nodes_check';

interface Props {
jobType: JobType;
}

export const NewJobAwaitingNodeWarning: FC<Props> = () => {
if (lazyMlNodesAvailable() === false) {
return null;
}

return (
<Fragment>
<EuiCallOut
title={
<FormattedMessage
id="xpack.ml.jobsAwaitingNodeWarning.title"
defaultMessage="Awaiting ML node provisioning"
defaultMessage="Awaiting machine learning node"
/>
}
color="primary"
Expand All @@ -31,7 +36,7 @@ export const NewJobAwaitingNodeWarning: FC<Props> = () => {
<div>
<FormattedMessage
id="xpack.ml.newJobAwaitingNodeWarning.noMLNodesAvailableDescription"
defaultMessage="Job cannot be started straight away, an ML node needs to be started. This will happen automatically."
defaultMessage="There are currently no nodes that can run the job, therefore it will remain in OPENING state until an appropriate node becomes available."
/>
</div>
</EuiCallOut>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -151,8 +151,8 @@ export const JobItem: FC<JobItemProps> = memo(

<EuiFlexItem grow={false}>
<EuiIcon
type={datafeedResult.started ? 'check' : 'cross'}
color={datafeedResult.started ? 'secondary' : 'danger'}
type={getDatafeedStartedIcon(datafeedResult).type}
color={getDatafeedStartedIcon(datafeedResult).color}
size="m"
aria-label={
setupResult.success
Expand All @@ -172,3 +172,14 @@ export const JobItem: FC<JobItemProps> = 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' };
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -84,6 +85,7 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
const [saveState, setSaveState] = useState<SAVE_STATE>(SAVE_STATE.NOT_SAVED);
const [resultsUrl, setResultsUrl] = useState<string>('');
const [existingGroups, setExistingGroups] = useState(existingGroupIds);
const [jobsAwaitingNodeCount, setJobsAwaitingNodeCount] = useState(0);
// #endregion

const {
Expand Down Expand Up @@ -204,9 +206,19 @@ export const Page: FC<PageProps> = ({ 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
Expand Down Expand Up @@ -291,6 +303,8 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
</>
)}

{jobsAwaitingNodeCount > 0 && <JobsAwaitingNodeWarning jobCount={jobsAwaitingNodeCount} />}

<EuiFlexGroup wrap={true} gutterSize="m">
<EuiFlexItem grow={1}>
<EuiPanel grow={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export {
checkMlNodesAvailable,
getMlNodeCount,
mlNodesAvailable,
lazyMlNodesAvailable,
permissionToViewMlNodeCount,
} from './check_ml_nodes';
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(
Expand Down
28 changes: 24 additions & 4 deletions x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
};
}
})
);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down