mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ML] Prompt the user to delete alerting rules upon the anomaly detection job deletion (#176049)
## Summary
Closes #174513
- Adds a control to the job deletion dialog for deleting associated
alerting rules
- Update the delete Kibana endpoint with a flag to delete alerting rules
<img width="1360" alt="image"
src="3d22b9a6
-9203-4583-b117-dfcd9087f373">
### Checklist
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
This commit is contained in:
parent
dba4086d26
commit
2c89f974a7
9 changed files with 78 additions and 20 deletions
|
@ -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,
|
||||
|
@ -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') {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
}
|
||||
)}
|
||||
checked={deleteAlertingRules}
|
||||
onChange={(e) => setDeleteAlertingRules(e.target.checked)}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</EuiText>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -84,10 +84,37 @@ export function jobsProvider(
|
|||
});
|
||||
}
|
||||
|
||||
async function deleteJobs(jobIds: string[], deleteUserAnnotations = false) {
|
||||
async function deleteJobs(
|
||||
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 =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue