From 21dbbd06366924b115bfba612b9f9bbc32ed2b1a Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian Date: Tue, 20 Jun 2023 17:04:43 +0200 Subject: [PATCH] [Security Solution] Reintroduce ML Jobs warning popover on rule upgrade (#159868) ## Summary Reintroduces ML Jobs warning popover that was removed during [new install/upgrade initial implementation.](https://github.com/elastic/kibana/pull/158450) ML Jobs warning popover that appears when user has legacy ML jobs installed, and the user attempts to update their prebuilt rules. Modified behaviour so that the popover now appears in any of the three cases: upgrading **all rules**, upgrading **specific rules** and upgrading **a single rule**. ### Testing In the `state` returned by `UpgradePrebuiltRulesTableContextProvider`, replace the value of `legacyJobsInstalled` for a mock value. See below: ```ts return { state: { rules, tags, isFetched, isLoading: isLoading && loadingJobs, isRefetching, selectedRules, loadingRules, lastUpdated: dataUpdatedAt, legacyJobsInstalled: [ { id: 'rc-rare-process-windows-5', description: 'Looks for rare and anomalous processes on a Windows host. Requires process execution events from Sysmon.', groups: ['host'], processed_record_count: 8577, memory_status: 'ok', jobState: 'closed', hasDatafeed: true, datafeedId: 'datafeed-rc-rare-process-windows-5', datafeedIndices: ['winlogbeat-*'], datafeedState: 'stopped', latestTimestampMs: 1561402325194, earliestTimestampMs: 1554327458406, isSingleMetricViewerJob: true, awaitingNodeAssignment: false, jobTags: {}, bucketSpanSeconds: 900, }, { id: 'siem-api-rare_process_linux_ecs', description: 'SIEM Auditbeat: Detect unusually rare processes on Linux (beta)', groups: ['siem'], processed_record_count: 582251, memory_status: 'hard_limit', jobState: 'closed', hasDatafeed: true, datafeedId: 'datafeed-siem-api-rare_process_linux_ecs', datafeedIndices: ['auditbeat-*'], datafeedState: 'stopped', latestTimestampMs: 1557434782207, earliestTimestampMs: 1557353420495, isSingleMetricViewerJob: true, awaitingNodeAssignment: false, jobTags: {}, bucketSpanSeconds: 900, }, ], isUpgradeModalVisible, ruleIdToUpgrade, modalConfirmationUpdateMethod, }, actions, }; ``` ### Checklist Delete any items that are not applicable to this PR. - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] 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)) - [ ] 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)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../upgrade_prebuilt_rules_table_context.tsx | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx index 89799a7e52dbc..1e4af49c53254 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/upgrade_prebuilt_rules_table/upgrade_prebuilt_rules_table_context.tsx @@ -5,9 +5,11 @@ * 2.0. */ -// import { isEqual } from 'lodash'; import type { Dispatch, SetStateAction } from 'react'; import React, { createContext, useCallback, useContext, useMemo, useState } from 'react'; +import { useInstalledSecurityJobs } from '../../../../../common/components/ml/hooks/use_installed_security_jobs'; +import { useBoolState } from '../../../../../common/hooks/use_bool_state'; +import { affectedJobIds } from '../../../../../detections/components/callouts/ml_job_compatibility_callout/affected_job_ids'; import type { RuleUpgradeInfoForReview } from '../../../../../../common/detection_engine/prebuilt_rules/api/review_rule_upgrade/response_schema'; import type { RuleSignatureId } from '../../../../../../common/detection_engine/rule_schema'; import { invariant } from '../../../../../../common/utils/invariant'; @@ -18,6 +20,9 @@ import { import { usePrebuiltRulesUpgradeReview } from '../../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_upgrade_review'; import type { UpgradePrebuiltRulesTableFilterOptions } from './use_filter_prebuilt_rules_to_upgrade'; import { useFilterPrebuiltRulesToUpgrade } from './use_filter_prebuilt_rules_to_upgrade'; +import { useAsyncConfirmation } from '../rules_table/use_async_confirmation'; + +import { MlJobUpgradeModal } from '../../../../../detections/components/modals/ml_job_upgrade_modal'; export interface UpgradePrebuiltRulesTableState { /** @@ -90,7 +95,6 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ }: UpgradePrebuiltRulesTableContextProviderProps) => { const [loadingRules, setLoadingRules] = useState([]); const [selectedRules, setSelectedRules] = useState([]); - const [filterOptions, setFilterOptions] = useState({ filter: '', tags: [], @@ -114,6 +118,19 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ const { mutateAsync: upgradeAllRulesRequest } = usePerformUpgradeAllRules(); const { mutateAsync: upgradeSpecificRulesRequest } = usePerformUpgradeSpecificRules(); + // Wrapper to add confirmation modal for users who may be running older ML Jobs that would + // be overridden by updating their rules. For details, see: https://github.com/elastic/kibana/issues/128121 + const [isUpgradeModalVisible, showUpgradeModal, hideUpgradeModal] = useBoolState(false); + const { loading: loadingJobs, jobs } = useInstalledSecurityJobs(); + const legacyJobsInstalled = jobs.filter((job) => affectedJobIds.includes(job.id)); + + const [confirmUpgrade, handleUpgradeConfirm, handleUpgradeCancel] = useAsyncConfirmation({ + onInit: showUpgradeModal, + onFinish: hideUpgradeModal, + }); + + const shouldConfirmUpgrade = legacyJobsInstalled.length > 0; + const upgradeOneRule = useCallback( async (ruleId: RuleSignatureId) => { const rule = rules.find((r) => r.rule_id === ruleId); @@ -121,6 +138,9 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ setLoadingRules((prev) => [...prev, ruleId]); try { + if (shouldConfirmUpgrade && !(await confirmUpgrade())) { + return; + } await upgradeSpecificRulesRequest([ { rule_id: ruleId, @@ -132,7 +152,7 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ setLoadingRules((prev) => prev.filter((id) => id !== ruleId)); } }, - [rules, upgradeSpecificRulesRequest] + [confirmUpgrade, rules, shouldConfirmUpgrade, upgradeSpecificRulesRequest] ); const upgradeSelectedRules = useCallback(async () => { @@ -143,23 +163,29 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ })); setLoadingRules((prev) => [...prev, ...rulesToUpgrade.map((r) => r.rule_id)]); try { + if (shouldConfirmUpgrade && !(await confirmUpgrade())) { + return; + } await upgradeSpecificRulesRequest(rulesToUpgrade); } finally { setLoadingRules((prev) => prev.filter((id) => !rulesToUpgrade.some((r) => r.rule_id === id))); setSelectedRules([]); } - }, [selectedRules, upgradeSpecificRulesRequest]); + }, [confirmUpgrade, selectedRules, shouldConfirmUpgrade, upgradeSpecificRulesRequest]); const upgradeAllRules = useCallback(async () => { // Unselect all rules so that the table doesn't show the "bulk actions" bar setLoadingRules((prev) => [...prev, ...rules.map((r) => r.rule_id)]); try { + if (shouldConfirmUpgrade && !(await confirmUpgrade())) { + return; + } await upgradeAllRulesRequest(); } finally { setLoadingRules([]); setSelectedRules([]); } - }, [rules, upgradeAllRulesRequest]); + }, [confirmUpgrade, rules, shouldConfirmUpgrade, upgradeAllRulesRequest]); const actions = useMemo( () => ({ @@ -170,7 +196,7 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ setFilterOptions, selectRules: setSelectedRules, }), - [refetch, upgradeAllRules, upgradeOneRule, upgradeSelectedRules] + [refetch, upgradeOneRule, upgradeSelectedRules, upgradeAllRules] ); const filteredRules = useFilterPrebuiltRulesToUpgrade({ filterOptions, rules }); @@ -183,11 +209,13 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ filterOptions, tags, isFetched, - isLoading, + isLoading: isLoading && loadingJobs, isRefetching, selectedRules, loadingRules, lastUpdated: dataUpdatedAt, + legacyJobsInstalled, + isUpgradeModalVisible, }, actions, }; @@ -198,15 +226,25 @@ export const UpgradePrebuiltRulesTableContextProvider = ({ tags, isFetched, isLoading, + loadingJobs, isRefetching, selectedRules, loadingRules, dataUpdatedAt, + legacyJobsInstalled, + isUpgradeModalVisible, actions, ]); return ( + {isUpgradeModalVisible && ( + + )} {children} );