mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* [ML] Adding close jobs menu item * changing icon * updates based on review * adding extra check for close failure * adding extra guard against missing response
This commit is contained in:
parent
fe2f671d0a
commit
d19079f6e3
7 changed files with 113 additions and 5 deletions
|
@ -10,8 +10,10 @@ import { mlNodesAvailable } from 'plugins/ml/ml_nodes_check/check_ml_nodes';
|
|||
import {
|
||||
stopDatafeeds,
|
||||
cloneJob,
|
||||
closeJobs,
|
||||
isStartable,
|
||||
isStoppable,
|
||||
isClosable,
|
||||
} from '../utils';
|
||||
|
||||
export function actionsMenuContent(showEditJobFlyout, showDeleteJobModal, showStartDatafeedModal, refreshJobs) {
|
||||
|
@ -20,6 +22,7 @@ export function actionsMenuContent(showEditJobFlyout, showDeleteJobModal, showSt
|
|||
const canDeleteJob = checkPermission('canDeleteJob');
|
||||
const canUpdateDatafeed = checkPermission('canUpdateDatafeed');
|
||||
const canStartStopDatafeed = (checkPermission('canStartStopDatafeed') && mlNodesAvailable());
|
||||
const canCloseJob = (checkPermission('canCloseJob') && mlNodesAvailable());
|
||||
|
||||
return [
|
||||
{
|
||||
|
@ -42,6 +45,16 @@ export function actionsMenuContent(showEditJobFlyout, showDeleteJobModal, showSt
|
|||
stopDatafeeds([item], refreshJobs);
|
||||
closeMenu(true);
|
||||
}
|
||||
}, {
|
||||
name: 'Close job',
|
||||
description: 'Close job',
|
||||
icon: 'cross',
|
||||
enabled: () => (canCloseJob),
|
||||
available: (item) => (isClosable([item])),
|
||||
onClick: (item) => {
|
||||
closeJobs([item], refreshJobs);
|
||||
closeMenu(true);
|
||||
}
|
||||
}, {
|
||||
name: 'Clone job',
|
||||
description: 'Clone job',
|
||||
|
|
|
@ -20,9 +20,12 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import {
|
||||
closeJobs,
|
||||
stopDatafeeds,
|
||||
isStartable,
|
||||
isStoppable } from '../utils';
|
||||
isStoppable,
|
||||
isClosable,
|
||||
} from '../utils';
|
||||
|
||||
export class MultiJobActionsMenu extends Component {
|
||||
constructor(props) {
|
||||
|
@ -34,6 +37,7 @@ export class MultiJobActionsMenu extends Component {
|
|||
|
||||
this.canDeleteJob = checkPermission('canDeleteJob');
|
||||
this.canStartStopDatafeed = (checkPermission('canStartStopDatafeed') && mlNodesAvailable());
|
||||
this.canCloseJob = (checkPermission('canCloseJob') && mlNodesAvailable());
|
||||
}
|
||||
|
||||
onButtonClick = () => {
|
||||
|
@ -74,6 +78,19 @@ export class MultiJobActionsMenu extends Component {
|
|||
)
|
||||
];
|
||||
|
||||
if(isClosable(this.props.jobs)) {
|
||||
items.push(
|
||||
<EuiContextMenuItem
|
||||
key="close job"
|
||||
icon="cross"
|
||||
disabled={(this.canCloseJob === false)}
|
||||
onClick={() => { closeJobs(this.props.jobs); this.closePopover(); }}
|
||||
>
|
||||
Close job{s}
|
||||
</EuiContextMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
if(isStoppable(this.props.jobs)) {
|
||||
items.push(
|
||||
<EuiContextMenuItem
|
||||
|
|
|
@ -10,7 +10,7 @@ import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar
|
|||
|
||||
import { mlJobService } from 'plugins/ml/services/job_service';
|
||||
import { ml } from 'plugins/ml/services/ml_api_service';
|
||||
import { DATAFEED_STATE } from 'plugins/ml/../common/constants/states';
|
||||
import { JOB_STATE, DATAFEED_STATE } from 'plugins/ml/../common/constants/states';
|
||||
|
||||
export function loadFullJob(jobId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -29,11 +29,15 @@ export function loadFullJob(jobId) {
|
|||
}
|
||||
|
||||
export function isStartable(jobs) {
|
||||
return (jobs.find(j => j.datafeedState === DATAFEED_STATE.STOPPED) !== undefined);
|
||||
return jobs.some(j => j.datafeedState === DATAFEED_STATE.STOPPED);
|
||||
}
|
||||
|
||||
export function isStoppable(jobs) {
|
||||
return (jobs.find(j => j.datafeedState === DATAFEED_STATE.STARTED) !== undefined);
|
||||
return jobs.some(j => j.datafeedState === DATAFEED_STATE.STARTED);
|
||||
}
|
||||
|
||||
export function isClosable(jobs) {
|
||||
return jobs.some(j => (j.datafeedState === DATAFEED_STATE.STOPPED) && (j.jobState !== JOB_STATE.CLOSED));
|
||||
}
|
||||
|
||||
export function forceStartDatafeeds(jobs, start, end, finish = () => {}) {
|
||||
|
@ -69,7 +73,8 @@ function showResults(resp, action) {
|
|||
const successes = [];
|
||||
const failures = [];
|
||||
for (const d in resp) {
|
||||
if (resp[d][action] === true || (resp[d][action] === false && resp[d].error.statusCode === 409)) {
|
||||
if (resp[d][action] === true ||
|
||||
(resp[d][action] === false && (resp[d].error.statusCode === 409 && action === DATAFEED_STATE.STARTED))) {
|
||||
successes.push(d);
|
||||
} else {
|
||||
failures.push({
|
||||
|
@ -90,8 +95,12 @@ function showResults(resp, action) {
|
|||
} else if (action === DATAFEED_STATE.DELETED) {
|
||||
actionText = 'delete';
|
||||
actionTextPT = 'deleted';
|
||||
} else if (action === JOB_STATE.CLOSED) {
|
||||
actionText = 'close';
|
||||
actionTextPT = 'closed';
|
||||
}
|
||||
|
||||
|
||||
if (successes.length > 1) {
|
||||
toastNotifications.addSuccess(`${successes.length} jobs ${actionTextPT} successfully`);
|
||||
} else if (successes.length === 1) {
|
||||
|
@ -118,6 +127,20 @@ export function cloneJob(jobId) {
|
|||
});
|
||||
}
|
||||
|
||||
export function closeJobs(jobs, finish = () => {}) {
|
||||
const jobIds = jobs.map(j => j.id);
|
||||
mlJobService.closeJobs(jobIds)
|
||||
.then((resp) => {
|
||||
showResults(resp, JOB_STATE.CLOSED);
|
||||
finish();
|
||||
})
|
||||
.catch((error) => {
|
||||
mlMessageBarService.notify.error(error);
|
||||
toastNotifications.addDanger(`Jobs failed to close`, error);
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteJobs(jobs, finish = () => {}) {
|
||||
const jobIds = jobs.map(j => j.id);
|
||||
mlJobService.deleteJobs(jobIds)
|
||||
|
|
|
@ -927,6 +927,9 @@ class JobService {
|
|||
return ml.jobs.deleteJobs(jIds);
|
||||
}
|
||||
|
||||
closeJobs(jIds) {
|
||||
return ml.jobs.closeJobs(jIds);
|
||||
}
|
||||
|
||||
validateDetector(detector) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
|
@ -71,6 +71,16 @@ export const jobs = {
|
|||
});
|
||||
},
|
||||
|
||||
closeJobs(jobIds) {
|
||||
return http({
|
||||
url: `${basePath}/jobs/close_jobs`,
|
||||
method: 'POST',
|
||||
data: {
|
||||
jobIds,
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
jobAuditMessages(jobId, from) {
|
||||
const jobIdString = (jobId !== undefined) ? `/${jobId}` : '';
|
||||
const fromString = (from !== undefined) ? `?from=${from}` : '';
|
||||
|
|
|
@ -47,6 +47,31 @@ export function jobsProvider(callWithRequest) {
|
|||
return results;
|
||||
}
|
||||
|
||||
async function closeJobs(jobIds) {
|
||||
const results = {};
|
||||
for (const jobId of jobIds) {
|
||||
try {
|
||||
await callWithRequest('ml.closeJob', { jobId });
|
||||
results[jobId] = { closed: true };
|
||||
} catch (error) {
|
||||
if (error.statusCode === 409 && (error.response && error.response.includes('datafeed') === false)) {
|
||||
// the close job request may fail (409) if the job has failed or if the datafeed hasn't been stopped.
|
||||
// if the job has failed we want to attempt a force close.
|
||||
// however, if we received a 409 due to the datafeed being started we should not attempt a force close.
|
||||
try {
|
||||
await callWithRequest('ml.closeJob', { jobId, force: true });
|
||||
results[jobId] = { closed: true };
|
||||
} catch (error2) {
|
||||
results[jobId] = { closed: false, error: error2 };
|
||||
}
|
||||
} else {
|
||||
results[jobId] = { closed: false, error };
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async function jobsSummary(jobIds = []) {
|
||||
const fullJobsList = await createFullJobsList();
|
||||
const auditMessages = await getAuditMessagesSummary();
|
||||
|
@ -276,6 +301,7 @@ export function jobsProvider(callWithRequest) {
|
|||
return {
|
||||
forceDeleteJob,
|
||||
deleteJobs,
|
||||
closeJobs,
|
||||
jobsSummary,
|
||||
createFullJobsList,
|
||||
getAllGroups,
|
||||
|
|
|
@ -63,6 +63,22 @@ export function jobServiceRoutes(server, commonRouteConfig) {
|
|||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/ml/jobs/close_jobs',
|
||||
handler(request, reply) {
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
const { closeJobs } = jobServiceProvider(callWithRequest);
|
||||
const { jobIds } = request.payload;
|
||||
return closeJobs(jobIds)
|
||||
.then(resp => reply(resp))
|
||||
.catch(resp => reply(wrapError(resp)));
|
||||
},
|
||||
config: {
|
||||
...commonRouteConfig
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/ml/jobs/jobs_summary',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue