Skip to content

Commit

Permalink
[ML] Adding reset anomaly detection jobs link to jobs list (#108039)
Browse files Browse the repository at this point in the history
* [ML] Adding reset jobs link to jobs list

* fixing types

* updating types

* improving react code

* adding closed job warning callout

* small code changes after review

* updating comment for api docs

* adding canResetJob to security's emptyMlCapabilities

* updating apidoc

* adding blocked to job summary

* udating test

* adding delayed refresh back in

* updating tests

* adding better reverting controls and labels

* fixing bug in delete modal

* updating job task polling for all blocking tasks

* fixing types after es client update

* one other type correction

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
jgowdyelastic and kibanamachine committed Aug 17, 2021
1 parent dd33a55 commit 61d1e56
Show file tree
Hide file tree
Showing 33 changed files with 637 additions and 93 deletions.
43 changes: 43 additions & 0 deletions x-pack/plugins/ml/common/constants/job_actions.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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;
3 changes: 3 additions & 0 deletions x-pack/plugins/ml/common/types/anomaly_detection_jobs/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import { estypes } from '@elastic/elasticsearch';
export type JobId = string;
export type BucketSpan = string;

// temporary Job override, waiting for es client to have correct types
export type Job = estypes.MlJob;

export type MlJobBlocked = estypes.MlJobBlocked;

export type AnalysisConfig = estypes.MlAnalysisConfig;

export type Detector = estypes.MlDetector;
Expand Down
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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;
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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,
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 @@ -340,6 +340,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

0 comments on commit 61d1e56

Please sign in to comment.