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] Prompts the user to delete alerting rules upon the anomaly detection job deletion #176049

Merged
merged 7 commits into from
Feb 2, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { FC, useState, useEffect, useCallback } from 'react';
import React, { FC, useState, useEffect, useCallback, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiSpacer,
Expand Down Expand Up @@ -40,10 +40,10 @@ interface Props {
export const DeleteJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction, refreshJobs }) => {
const [deleting, setDeleting] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [jobIds, setJobIds] = useState<string[]>([]);
const [adJobs, setAdJobs] = useState<MlSummaryJob[]>([]);
const [canDelete, setCanDelete] = useState(false);
const [hasManagedJob, setHasManagedJob] = useState(false);
const [deleteUserAnnotations, setDeleteUserAnnotations] = useState(false);
const [deleteAlertingRules, setDeleteAlertingRules] = useState(false);

useEffect(() => {
if (typeof setShowFunction === 'function') {
Expand All @@ -58,13 +58,22 @@ export const DeleteJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction,
}, []);

const showModal = useCallback((jobs: MlSummaryJob[]) => {
setJobIds(jobs.map(({ id }) => id));
setHasManagedJob(jobs.some((job) => isManagedJob(job)));
setAdJobs(jobs);
setModalVisible(true);
setDeleting(false);
setDeleteUserAnnotations(false);
}, []);

const { jobIds, hasManagedJob, hasAlertingRules } = useMemo(() => {
return {
jobIds: adJobs.map(({ id }) => id),
hasManagedJob: adJobs.some((job) => isManagedJob(job)),
hasAlertingRules: adJobs.some(
(job) => Array.isArray(job.alertingRules) && job.alertingRules.length > 0
),
};
}, [adJobs]);

const closeModal = useCallback(() => {
setModalVisible(false);
setCanDelete(false);
Expand All @@ -74,14 +83,15 @@ export const DeleteJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction,
setDeleting(true);
deleteJobs(
jobIds.map((id) => ({ id })),
deleteUserAnnotations
deleteUserAnnotations,
deleteAlertingRules
);

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

if (modalVisible === false || jobIds.length === 0) {
return null;
Expand Down Expand Up @@ -143,13 +153,28 @@ export const DeleteJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction,
label={i18n.translate(
'xpack.ml.jobsList.deleteJobModal.deleteUserAnnotations',
{
defaultMessage: 'Delete annotations.',
defaultMessage: 'Delete annotations',
}
)}
checked={deleteUserAnnotations}
onChange={(e) => setDeleteUserAnnotations(e.target.checked)}
data-test-subj="mlDeleteJobConfirmModalDeleteAnnotationsSwitch"
/>
{hasAlertingRules ? (
<>
<EuiSpacer size={'s'} />
<EuiSwitch
label={i18n.translate(
'xpack.ml.jobsList.resetJobModal.deleteAlertingRules',
{
defaultMessage: 'Delete alerting rules',
darnautov marked this conversation as resolved.
Show resolved Hide resolved
}
)}
checked={deleteAlertingRules}
onChange={(e) => setDeleteAlertingRules(e.target.checked)}
/>
</>
) : null}
</EuiText>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export const ResetJobModal: FC<Props> = ({ setShowFunction, unsetShowFunction, r
<EuiSpacer />
<EuiSwitch
label={i18n.translate('xpack.ml.jobsList.resetJobModal.deleteUserAnnotations', {
defaultMessage: 'Delete annotations.',
defaultMessage: 'Delete annotations',
})}
checked={deleteUserAnnotations}
onChange={(e) => setDeleteUserAnnotations(e.target.checked)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function closeJobs(jobs: Array<{ id: string }>, callback?: () => void): P
export function deleteJobs(
jobs: Array<{ id: string }>,
deleteUserAnnotations?: boolean,
deleteAlertingRules?: boolean,
callback?: () => void
): Promise<void>;
export function resetJobs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,10 +326,10 @@ export function resetJobs(jobIds, deleteUserAnnotations, finish = () => {}) {
});
}

export function deleteJobs(jobs, deleteUserAnnotations, finish = () => {}) {
export function deleteJobs(jobs, deleteUserAnnotations, deleteAlertingRules, finish = () => {}) {
const jobIds = jobs.map((j) => j.id);
mlJobService
.deleteJobs(jobIds, deleteUserAnnotations)
.deleteJobs(jobIds, deleteUserAnnotations, deleteAlertingRules)
.then((resp) => {
showResults(resp, JOB_STATE.DELETED);
finish();
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/ml/public/application/services/job_service.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ class JobService {
return ml.jobs.stopDatafeeds(dIds);
}

deleteJobs(jIds, deleteUserAnnotations) {
return ml.jobs.deleteJobs(jIds, deleteUserAnnotations);
deleteJobs(jIds, deleteUserAnnotations, deleteAlertingRules) {
return ml.jobs.deleteJobs(jIds, deleteUserAnnotations, deleteAlertingRules);
}

closeJobs(jIds) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ export const jobsApiProvider = (httpService: HttpService) => ({
});
},

deleteJobs(jobIds: string[], deleteUserAnnotations?: boolean) {
const body = JSON.stringify({ jobIds, deleteUserAnnotations });
deleteJobs(jobIds: string[], deleteUserAnnotations?: boolean, deleteAlertingRules?: boolean) {
const body = JSON.stringify({ jobIds, deleteUserAnnotations, deleteAlertingRules });
return httpService.http<any>({
path: `${ML_INTERNAL_BASE_PATH}/jobs/delete_jobs`,
method: 'POST',
Expand Down
29 changes: 28 additions & 1 deletion x-pack/plugins/ml/server/models/job_service/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,37 @@ export function jobsProvider(
});
}

async function deleteJobs(jobIds: string[], deleteUserAnnotations = false) {
async function deleteJobs(
peteharverson marked this conversation as resolved.
Show resolved Hide resolved
jobIds: string[],
deleteUserAnnotations = false,
deleteAlertingRules = false
) {
const results: Results = {};
const datafeedIds = await getDatafeedIdsByJobId();

if (deleteAlertingRules && rulesClient) {
// Check what jobs have associated alerting rules
const anomalyDetectionAlertingRules = await rulesClient.find<MlAnomalyDetectionAlertParams>({
options: {
filter: `alert.attributes.alertTypeId:${ML_ALERT_TYPES.ANOMALY_DETECTION}`,
perPage: 10000,
},
});

const jobIdsSet = new Set(jobIds);
const ruleIds: string[] = anomalyDetectionAlertingRules.data
.filter((rule) => {
return jobIdsSet.has(rule.params.jobSelection.jobIds[0]);
})
.map((rule) => rule.id);

if (ruleIds.length > 0) {
await rulesClient.bulkDeleteRules({
ids: ruleIds,
});
}
}

for (const jobId of jobIds) {
try {
const datafeedResp =
Expand Down
12 changes: 8 additions & 4 deletions x-pack/plugins/ml/server/routes/job_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,15 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) {
tags: ['access:ml:canDeleteJob'],
},
},
routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response }) => {
routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response, context }) => {
try {
const { deleteJobs } = jobServiceProvider(client, mlClient);
const { jobIds, deleteUserAnnotations } = request.body;
const resp = await deleteJobs(jobIds, deleteUserAnnotations);
const alerting = await context.alerting;
const rulesClient = alerting?.getRulesClient();
const { deleteJobs } = jobServiceProvider(client, mlClient, rulesClient);

const { jobIds, deleteUserAnnotations, deleteAlertingRules } = request.body;

const resp = await deleteJobs(jobIds, deleteUserAnnotations, deleteAlertingRules);

return response.ok({
body: resp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const deleteJobsSchema = schema.object({
/** List of job IDs. */
jobIds: schema.arrayOf(schema.string()),
deleteUserAnnotations: schema.maybe(schema.boolean()),
deleteAlertingRules: schema.maybe(schema.boolean()),
});

export const optionalJobIdsSchema = schema.object({
Expand Down