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] Adding reset anomaly detection jobs link to jobs list #108039

Merged
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6dfe7e0
[ML] Adding reset jobs link to jobs list
jgowdyelastic Aug 10, 2021
6dc576f
fixing types
jgowdyelastic Aug 10, 2021
9a0af18
Merge branch 'master' into adding-reset-job-links-to-job-list
kibanamachine Aug 11, 2021
d4b3a78
updating types
jgowdyelastic Aug 11, 2021
a66e429
improving react code
jgowdyelastic Aug 11, 2021
e6d3a2a
adding closed job warning callout
jgowdyelastic Aug 11, 2021
06f04dc
small code changes after review
jgowdyelastic Aug 11, 2021
4f6220d
updating comment for api docs
jgowdyelastic Aug 11, 2021
4e150e5
adding canResetJob to security's emptyMlCapabilities
jgowdyelastic Aug 11, 2021
268098f
updating apidoc
jgowdyelastic Aug 11, 2021
797c274
adding blocked to job summary
jgowdyelastic Aug 11, 2021
c78d183
udating test
jgowdyelastic Aug 11, 2021
115ad46
adding delayed refresh back in
jgowdyelastic Aug 11, 2021
1864380
updating tests
jgowdyelastic Aug 12, 2021
25c8085
Merge branch 'master' into adding-reset-job-links-to-job-list
kibanamachine Aug 12, 2021
d8a0969
adding better reverting controls and labels
jgowdyelastic Aug 12, 2021
a7b9e2e
fixing bug in delete modal
jgowdyelastic Aug 12, 2021
8841935
updating job task polling for all blocking tasks
jgowdyelastic Aug 13, 2021
a2ad0a8
Merge branch 'master' into adding-reset-job-links-to-job-list
kibanamachine Aug 16, 2021
5e3bfe7
Merge branch 'master' into adding-reset-job-links-to-job-list
kibanamachine Aug 17, 2021
c709342
Merge branch 'master' into adding-reset-job-links-to-job-list
kibanamachine Aug 17, 2021
a40bed4
fixing types after es client update
jgowdyelastic Aug 17, 2021
4c2ba81
one other type correction
jgowdyelastic Aug 17, 2021
fb4fd17
Merge branch 'master' into adding-reset-job-links-to-job-list
kibanamachine Aug 17, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 43 additions & 0 deletions x-pack/plugins/ml/common/constants/job_actions.ts
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';

export const JOB_ACTION = {
DELETE: 'delete',
RESET: 'reset',
REVERT: 'revert',
} as const;

export type JobAction = typeof JOB_ACTION[keyof typeof JOB_ACTION];

export function getJobActionString(action: JobAction) {
switch (action) {
case JOB_ACTION.DELETE:
return i18n.translate('xpack.ml.models.jobService.deletingJob', {
defaultMessage: 'deleting',
});
case JOB_ACTION.RESET:
return i18n.translate('xpack.ml.models.jobService.resettingJob', {
defaultMessage: 'resetting',
});
case JOB_ACTION.REVERT:
return i18n.translate('xpack.ml.models.jobService.revertingJob', {
defaultMessage: 'reverting',
});
default:
return '';
}
}

export const JOB_ACTION_TASK: Record<string, JobAction> = {
'cluster:admin/xpack/ml/job/delete': JOB_ACTION.DELETE,
'cluster:admin/xpack/ml/job/reset': JOB_ACTION.RESET,
'cluster:admin/xpack/ml/job/model_snapshots/revert': JOB_ACTION.REVERT,
};

export const JOB_ACTION_TASKS = Object.keys(JOB_ACTION_TASK);
1 change: 1 addition & 0 deletions x-pack/plugins/ml/common/constants/jobs_list.ts
Expand Up @@ -8,4 +8,5 @@
export const DEFAULT_REFRESH_INTERVAL_MS = 30000;
export const MINIMUM_REFRESH_INTERVAL_MS = 1000;
export const DELETING_JOBS_REFRESH_INTERVAL_MS = 2000;
export const RESETTING_JOBS_REFRESH_INTERVAL_MS = 1000;
export const PROGRESS_JOBS_REFRESH_INTERVAL_MS = 2000;
10 changes: 9 additions & 1 deletion x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts
Expand Up @@ -10,7 +10,15 @@ import { estypes } from '@elastic/elasticsearch';
export type JobId = string;
export type BucketSpan = string;

export type Job = estypes.MlJob;
// temporary Job override, waiting for es client to have correct types
export type Job = estypes.MlJob & {
blocked: MlJobBlocked;
};

export interface MlJobBlocked {
reason: 'delete' | 'revert' | 'reset';
task_id?: string;
}

export type AnalysisConfig = estypes.MlAnalysisConfig;

Expand Down
Expand Up @@ -7,10 +7,11 @@

import { Moment } from 'moment';

import { CombinedJob, CombinedJobWithStats } from './combined_job';
import { MlAnomalyDetectionAlertRule } from '../alerts';
export { Datafeed } from './datafeed';
export { DatafeedStats } from './datafeed_stats';
import type { CombinedJob, CombinedJobWithStats } from './combined_job';
import type { MlAnomalyDetectionAlertRule } from '../alerts';
import type { MlJobBlocked } from './job';
export type { Datafeed } from './datafeed';
export type { DatafeedStats } from './datafeed_stats';

export interface MlSummaryJob {
id: string;
Expand All @@ -31,7 +32,7 @@ export interface MlSummaryJob {
auditMessage?: Partial<AuditMessage>;
isSingleMetricViewerJob: boolean;
isNotSingleMetricViewerJobMessage?: string;
deleting?: boolean;
blocked?: MlJobBlocked;
latestTimestampSortValue?: number;
earliestStartTimestampMs?: number;
awaitingNodeAssignment: boolean;
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/ml/common/types/capabilities.ts
Expand Up @@ -40,6 +40,7 @@ export const adminMlCapabilities = {
canDeleteJob: false,
canOpenJob: false,
canCloseJob: false,
canResetJob: false,
canUpdateJob: false,
canForecastJob: false,
canCreateDatafeed: false,
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/ml/common/types/job_service.ts
Expand Up @@ -48,3 +48,11 @@ export interface BulkCreateResults {
datafeed: { success: boolean; error?: ErrorType };
};
}

export interface ResetJobsResponse {
[jobId: string]: {
reset: boolean;
task?: string;
error?: ErrorType;
};
}
2 changes: 1 addition & 1 deletion x-pack/plugins/ml/common/util/errors/types.ts
Expand Up @@ -73,5 +73,5 @@ export function isMLResponseError(error: any): error is MLResponseError {
}

export function isBoomError(error: any): error is Boom.Boom {
return error.isBoom === true;
return error?.isBoom === true;
}
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { FC, useState, useEffect } from 'react';
import React, { FC, useState, useEffect, useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiSpacer,
Expand All @@ -23,8 +23,9 @@ import {
import { deleteJobs } from '../utils';
import { DELETING_JOBS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants/jobs_list';
import { DeleteJobCheckModal } from '../../../../components/delete_job_check_modal';
import { MlSummaryJob } from '../../../../../../common/types/anomaly_detection_jobs';

type ShowFunc = (jobs: Array<{ id: string }>) => void;
type ShowFunc = (jobs: MlSummaryJob[]) => void;

interface Props {
setShowFunction(showFunc: ShowFunc): void;
Expand All @@ -49,26 +50,26 @@ export const DeleteJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction,
};
}, []);

function showModal(jobs: any[]) {
const showModal = useCallback((jobs: MlSummaryJob[]) => {
setJobIds(jobs.map(({ id }) => id));
setModalVisible(true);
setDeleting(false);
}
}, []);

function closeModal() {
const closeModal = useCallback(() => {
setModalVisible(false);
setCanDelete(false);
}
}, []);

function deleteJob() {
const deleteJob = useCallback(() => {
setDeleting(true);
deleteJobs(jobIds.map((id) => ({ id })));

setTimeout(() => {
closeModal();
refreshJobs();
}, DELETING_JOBS_REFRESH_INTERVAL_MS);
}
}, [jobIds, refreshJobs]);

if (modalVisible === false || jobIds.length === 0) {
return null;
Expand Down
Expand Up @@ -8,14 +8,24 @@
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes';
import { getIndexPatternNames } from '../../../../util/index_utils';
import { JOB_ACTION } from '../../../../../../common/constants/job_actions';

import { stopDatafeeds, cloneJob, closeJobs, isStartable, isStoppable, isClosable } from '../utils';
import {
stopDatafeeds,
cloneJob,
closeJobs,
isStartable,
isStoppable,
isClosable,
isResettable,
} from '../utils';
import { getToastNotifications } from '../../../../util/dependency_cache';
import { i18n } from '@kbn/i18n';

export function actionsMenuContent(
showEditJobFlyout,
showDeleteJobModal,
showResetJobModal,
showStartDatafeedModal,
refreshJobs,
showCreateAlertFlyout
Expand All @@ -26,6 +36,7 @@ export function actionsMenuContent(
const canUpdateDatafeed = checkPermission('canUpdateDatafeed');
const canStartStopDatafeed = checkPermission('canStartStopDatafeed') && mlNodesAvailable();
const canCloseJob = checkPermission('canCloseJob') && mlNodesAvailable();
const canResetJob = checkPermission('canResetJob') && mlNodesAvailable();
const canCreateMlAlerts = checkPermission('canCreateMlAlerts');

return [
Expand All @@ -37,7 +48,7 @@ export function actionsMenuContent(
defaultMessage: 'Start datafeed',
}),
icon: 'play',
enabled: (item) => item.deleting !== true && canStartStopDatafeed,
enabled: (item) => isJobBlocked(item) === false && canStartStopDatafeed,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see these action items disabled after hitting reset. Is this to be expected, without the planned follow-up for polling?

reset_actions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

797c274 fixes this issue.
It's still tricky to reproduce as you need to catch the job while the reset task is running.
I think i might need to reinstate the slight delay to refresh the jobs after applying the reset, just to increase the chance that the first refresh will catch the the job why the task is running, and not before, which is happening now.
The follow up work will add a poll for the tasks so the jobs list will update once the task is complete.

image

Note, delete is still available. I've raised this as a question in a previous comment.

available: (item) => isStartable([item]),
onClick: (item) => {
showStartDatafeedModal([item]);
Expand All @@ -53,7 +64,7 @@ export function actionsMenuContent(
defaultMessage: 'Stop datafeed',
}),
icon: 'stop',
enabled: (item) => item.deleting !== true && canStartStopDatafeed,
enabled: (item) => isJobBlocked(item) === false && canStartStopDatafeed,
available: (item) => isStoppable([item]),
onClick: (item) => {
stopDatafeeds([item], refreshJobs);
Expand All @@ -69,7 +80,7 @@ export function actionsMenuContent(
defaultMessage: 'Create alert rule',
}),
icon: 'bell',
enabled: (item) => item.deleting !== true,
enabled: (item) => isJobBlocked(item) === false,
available: () => canCreateMlAlerts,
onClick: (item) => {
showCreateAlertFlyout([item.id]);
Expand All @@ -85,14 +96,30 @@ export function actionsMenuContent(
defaultMessage: 'Close job',
}),
icon: 'cross',
enabled: (item) => item.deleting !== true && canCloseJob,
enabled: (item) => isJobBlocked(item) === false && canCloseJob,
available: (item) => isClosable([item]),
onClick: (item) => {
closeJobs([item], refreshJobs);
closeMenu(true);
},
'data-test-subj': 'mlActionButtonCloseJob',
},
{
name: i18n.translate('xpack.ml.jobsList.managementActions.resetJobLabel', {
defaultMessage: 'Reset job',
}),
description: i18n.translate('xpack.ml.jobsList.managementActions.resetJobDescription', {
defaultMessage: 'Reset job',
}),
icon: 'refresh',
enabled: (item) => isResetEnabled(item) && canResetJob,
available: (item) => isResettable([item]),
onClick: (item) => {
showResetJobModal([item]);
closeMenu(true);
},
'data-test-subj': 'mlActionButtonResetJob',
},
{
name: i18n.translate('xpack.ml.jobsList.managementActions.cloneJobLabel', {
defaultMessage: 'Clone job',
Expand All @@ -106,7 +133,7 @@ export function actionsMenuContent(
// the indexPattern the job was created for. An indexPattern could either have been deleted
// since the the job was created or the current user doesn't have the required permissions to
// access the indexPattern.
return item.deleting !== true && canCreateJob;
return isJobBlocked(item) === false && canCreateJob;
},
onClick: (item) => {
const indexPatternNames = getIndexPatternNames();
Expand Down Expand Up @@ -136,7 +163,7 @@ export function actionsMenuContent(
defaultMessage: 'Edit job',
}),
icon: 'pencil',
enabled: (item) => item.deleting !== true && canUpdateJob && canUpdateDatafeed,
enabled: (item) => isJobBlocked(item) === false && canUpdateJob && canUpdateDatafeed,
onClick: (item) => {
showEditJobFlyout(item);
closeMenu();
Expand All @@ -162,6 +189,17 @@ export function actionsMenuContent(
];
}

function isResetEnabled(item) {
if (item.blocked === undefined || item.blocked.reason === JOB_ACTION.RESET) {
return true;
}
return false;
}

function isJobBlocked(item) {
return item.blocked !== undefined;
}

function closeMenu(now = false) {
if (now) {
document.querySelector('.euiTable').click();
Expand Down
Expand Up @@ -48,7 +48,7 @@ export function ResultLinks({ jobs }) {
},
})
: undefined;
const jobActionsDisabled = jobs.length === 1 && jobs[0].deleting === true;
const jobActionsDisabled = jobs.length === 1 && jobs[0].blocked !== undefined;
const { createLinkWithUserDefaults } = useCreateADLinks();
const timeSeriesExplorerLink = useMemo(
() => createLinkWithUserDefaults('timeseriesexplorer', jobs),
Expand Down
Expand Up @@ -104,7 +104,7 @@ export class JobsList extends Component {
render() {
const { loading, isManagementTable, spacesApi } = this.props;
const selectionControls = {
selectable: (job) => job.deleting !== true,
selectable: (job) => job.blocked === undefined,
selectableMessage: (selectable, rowItem) =>
selectable === false
? i18n.translate('xpack.ml.jobsList.cannotSelectRowForJobMessage', {
Expand Down Expand Up @@ -140,7 +140,7 @@ export class JobsList extends Component {
render: (item) => (
<EuiButtonIcon
onClick={() => this.toggleRow(item)}
isDisabled={item.deleting === true}
isDisabled={item.blocked !== undefined}
iconType={this.state.itemIdToExpandedRowMap[item.id] ? 'arrowDown' : 'arrowRight'}
aria-label={
this.state.itemIdToExpandedRowMap[item.id]
Expand Down Expand Up @@ -337,6 +337,7 @@ export class JobsList extends Component {
actions: actionsMenuContent(
this.props.showEditJobFlyout,
this.props.showDeleteJobModal,
this.props.showResetJobModal,
this.props.showStartDatafeedModal,
this.props.refreshJobs,
this.props.showCreateAlertFlyout
Expand Down