From 6cc4292eb624620644b75d12d04af5f3ce5f110a Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Thu, 13 Aug 2020 01:48:53 +0000 Subject: [PATCH 01/16] Proceed on conflict when updating alert status --- .../detection_engine/schemas/common/schemas.ts | 3 +++ .../request/set_signal_status_schema.ts | 3 ++- .../components/alerts_table/actions.tsx | 7 ++++++- .../components/alerts_table/index.tsx | 11 +++++++---- .../components/alerts_table/translations.ts | 18 +++++++++--------- .../components/alerts_table/types.ts | 7 ++++++- .../containers/detection_engine/alerts/api.ts | 2 +- .../routes/signals/open_close_signals_route.ts | 7 ++++++- 8 files changed, 40 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 2a0d1ef8b9dfde..bab4deb3dde183 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -270,6 +270,9 @@ export type Status = t.TypeOf; export const job_status = t.keyof({ succeeded: null, failed: null, 'going to run': null }); export type JobStatus = t.TypeOf; +export const conflicts = t.keyof({ abort: null, proceed: null }); +export type Conflicts = t.TypeOf; + // TODO: Create a regular expression type or custom date math part type here export const to = t.string; export type To = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/set_signal_status_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/set_signal_status_schema.ts index 1464896e502941..b039558d827bbc 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/set_signal_status_schema.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/set_signal_status_schema.ts @@ -6,13 +6,14 @@ import * as t from 'io-ts'; -import { signal_ids, signal_status_query, status } from '../common/schemas'; +import { conflicts, signal_ids, signal_status_query, status } from '../common/schemas'; export const setSignalsStatusSchema = t.intersection([ t.type({ status, }), t.partial({ + conflicts, signal_ids, query: signal_status_query, }), diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 34c0537a6d7d24..410e42d5908c99 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -83,7 +83,12 @@ export const updateAlertStatusAction = async ({ // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: alertIds, isDeleted: true }); - onAlertStatusUpdateSuccess(response.updated, selectedStatus); + onAlertStatusUpdateSuccess( + response.updated, + response.total, + response.version_conflicts, + selectedStatus + ); } catch (error) { onAlertStatusUpdateFailure(selectedStatus, error); } finally { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 66423259ec1556..0c3e1784f315ea 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -181,17 +181,20 @@ export const AlertsTableComponent: React.FC = ({ ); const onAlertStatusUpdateSuccess = useCallback( - (count: number, status: Status) => { + (updated: number, total: number, conflicts: number, status: Status) => { let title: string; switch (status) { case 'closed': - title = i18n.CLOSED_ALERT_SUCCESS_TOAST(count); + title = i18n.CLOSED_ALERT_SUCCESS_TOAST(updated, total); break; case 'open': - title = i18n.OPENED_ALERT_SUCCESS_TOAST(count); + title = i18n.OPENED_ALERT_SUCCESS_TOAST(updated, total); break; case 'in-progress': - title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(count); + title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(updated, total); + } + if (conflicts > 0) { + // TODO: add 'try again' button } displaySuccessToast(title, dispatchToaster); }, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index 3d6c3dc0a7a8e5..e0dbcb19858692 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -129,27 +129,27 @@ export const ACTION_ADD_ENDPOINT_EXCEPTION = i18n.translate( } ); -export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => +export const CLOSED_ALERT_SUCCESS_TOAST = (totalClosed: number, totalAlerts: number) => i18n.translate('xpack.securitySolution.detectionEngine.alerts.closedAlertSuccessToastMessage', { - values: { totalAlerts }, + values: { totalClosed, totalAlerts }, defaultMessage: - 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + 'Successfully closed {totalClosed}/{totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', }); -export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => +export const OPENED_ALERT_SUCCESS_TOAST = (totalOpened: number, totalAlerts: number) => i18n.translate('xpack.securitySolution.detectionEngine.alerts.openedAlertSuccessToastMessage', { - values: { totalAlerts }, + values: { totalOpened, totalAlerts }, defaultMessage: - 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + 'Successfully opened {totalOpened}/{totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', }); -export const IN_PROGRESS_ALERT_SUCCESS_TOAST = (totalAlerts: number) => +export const IN_PROGRESS_ALERT_SUCCESS_TOAST = (totalInProgress: number, totalAlerts: number) => i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.inProgressAlertSuccessToastMessage', { - values: { totalAlerts }, + values: { totalInProgress, totalAlerts }, defaultMessage: - 'Successfully marked {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}} as in progress.', + 'Successfully marked {totalInProgress}/{totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}} as in progress.', } ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts index 2e77e77f6b3d50..bf5532af98b10a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts @@ -45,7 +45,12 @@ export interface UpdateAlertStatusActionProps { selectedStatus: Status; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; - onAlertStatusUpdateSuccess: (count: number, status: Status) => void; + onAlertStatusUpdateSuccess: ( + count: number, + total: number, + conflicts: number, + status: Status + ) => void; onAlertStatusUpdateFailure: (status: Status, error: Error) => void; } diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts index 3fe676fe2c7d6f..a8a2ae10a3bbd1 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts @@ -58,7 +58,7 @@ export const updateAlertStatus = async ({ }: UpdateAlertStatusProps): Promise => KibanaServices.get().http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { method: 'POST', - body: JSON.stringify({ status, ...query }), + body: JSON.stringify({ conflicts: 'proceed', status, ...query }), signal, }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index ac5132d93a0698..1489fe467f0840 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -28,7 +28,7 @@ export const setSignalsStatusRoute = (router: IRouter) => { }, }, async (context, request, response) => { - const { signal_ids: signalIds, query, status } = request.body; + const { conflicts, signal_ids: signalIds, query, status } = request.body; const clusterClient = context.core.elasticsearch.legacy.client; const siemClient = context.securitySolution?.getAppClient(); const siemResponse = buildSiemResponse(response); @@ -52,9 +52,14 @@ export const setSignalsStatusRoute = (router: IRouter) => { }, }; } + let onConflict = 'abort'; + if (conflicts != null) { + onConflict = conflicts; + } try { const result = await clusterClient.callAsCurrentUser('updateByQuery', { index: siemClient.getSignalsIndex(), + conflicts: onConflict, refresh: 'wait_for', body: { script: { From 017c64a42c314da4a1f683b42868f9bfe4742ee2 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Wed, 19 Aug 2020 15:16:28 +0000 Subject: [PATCH 02/16] Handle conflicts --- .../public/common/hooks/use_app_toasts.ts | 5 +- .../components/alerts_table/actions.tsx | 1 - .../components/alerts_table/index.tsx | 53 +++++++++++++++---- .../components/alerts_table/translations.ts | 7 +++ .../components/alerts_table/types.ts | 1 - .../signals/open_close_signals_route.ts | 5 ++ 6 files changed, 57 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts index bc59d87100058a..ae811e7400737e 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts @@ -10,7 +10,7 @@ import { ErrorToastOptions, ToastsStart, Toast } from '../../../../../../src/cor import { useToasts } from '../lib/kibana'; import { isAppError, AppError } from '../utils/api'; -export type UseAppToasts = Pick & { +export type UseAppToasts = Pick & { api: ToastsStart; addError: (error: unknown, options: ErrorToastOptions) => Toast; }; @@ -19,6 +19,7 @@ export const useAppToasts = (): UseAppToasts => { const toasts = useToasts(); const addError = useRef(toasts.addError.bind(toasts)).current; const addSuccess = useRef(toasts.addSuccess.bind(toasts)).current; + const addWarning = useRef(toasts.addWarning.bind(toasts)).current; const addAppError = useCallback( (error: AppError, options: ErrorToastOptions) => @@ -44,5 +45,5 @@ export const useAppToasts = (): UseAppToasts => { [addAppError, addError] ); - return { api: toasts, addError: _addError, addSuccess }; + return { api: toasts, addError: _addError, addSuccess, addWarning }; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 410e42d5908c99..015883b441b0b2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -67,7 +67,6 @@ export const getFilterAndRuleBounds = ( export const updateAlertStatusAction = async ({ query, alertIds, - status, selectedStatus, setEventsLoading, setEventsDeleted, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 0c3e1784f315ea..ba0103200485ba 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiPanel, EuiLoadingContent } from '@elastic/eui'; +import { EuiButton, EuiPanel, EuiLoadingContent } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { connect, ConnectedProps, useDispatch } from 'react-redux'; import { Dispatch } from 'redux'; - +import { FormattedMessage } from 'react-intl'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { Filter, esQuery } from '../../../../../../../src/plugins/data/public'; import { TimelineIdLiteral } from '../../../../common/types/timeline'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/fetch_index_patterns'; import { StatefulEventsViewer } from '../../../common/components/events_viewer'; import { HeaderSection } from '../../../common/components/header_section'; @@ -44,6 +45,7 @@ import { SetEventsLoadingProps, UpdateAlertsStatusCallback, UpdateAlertsStatusProps, + UpdateAlertStatusActionProps, } from './types'; import { dispatchUpdateTimeline } from '../../../timelines/components/open_timeline/helpers'; import { @@ -56,6 +58,7 @@ import { AddExceptionModal, AddExceptionModalBaseProps, } from '../../../common/components/exceptions/add_exception_modal'; +import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; interface OwnProps { timelineId: TimelineIdLiteral; @@ -120,6 +123,10 @@ export const AlertsTableComponent: React.FC = ({ ); const kibana = useKibana(); const [, dispatchToaster] = useStateToaster(); + const { addWarning } = useAppToasts(); + const [lastUpdateRequest, setLastUpdateRequest] = useState< + Pick + >(null); const getGlobalQuery = useCallback( (customFilters: Filter[]) => { @@ -193,12 +200,25 @@ export const AlertsTableComponent: React.FC = ({ case 'in-progress': title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(updated, total); } + if (conflicts > 0) { - // TODO: add 'try again' button + addWarning({ + title: i18n.UPDATE_ALERT_STATUS_FAILED, + text: toMountPoint( + updateAlertStatusActionWrapper(lastUpdateRequest)}> + + + ), + toastLifeTimeMs: 9999999999, + }); + } else { + displaySuccessToast(title, dispatchToaster); } - displaySuccessToast(title, dispatchToaster); }, - [dispatchToaster] + [addWarning, dispatchToaster, lastUpdateRequest, updateAlertStatusActionWrapper] ); const onAlertStatusUpdateFailure = useCallback( @@ -282,26 +302,37 @@ export const AlertsTableComponent: React.FC = ({ { status, selectedStatus }: UpdateAlertsStatusProps ) => { const currentStatusFilter = buildAlertStatusFilter(status); - await updateAlertStatusAction({ + const updateRequest = { query: showClearSelectionAction ? getGlobalQuery(currentStatusFilter)?.filterQuery : undefined, alertIds: Object.keys(selectedEventIds), - status, selectedStatus, + }; + await updateAlertStatusActionWrapper(updateRequest); + refetchQuery(); + }, + [getGlobalQuery, selectedEventIds, showClearSelectionAction, updateAlertStatusActionWrapper] + ); + + const updateAlertStatusActionWrapper: ( + updateRequest: Pick + ) => void = useCallback( + async ( + updateRequest: Pick + ) => { + await updateAlertStatusAction({ + ...updateRequest, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, }); - refetchQuery(); + setLastUpdateRequest(updateRequest); }, [ - getGlobalQuery, - selectedEventIds, setEventsDeletedCallback, setEventsLoadingCallback, - showClearSelectionAction, onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, ] diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index e0dbcb19858692..bd7ec58ca10bd7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -173,3 +173,10 @@ export const IN_PROGRESS_ALERT_FAILED_TOAST = i18n.translate( defaultMessage: 'Failed to mark alert(s) as in progress', } ); + +export const UPDATE_ALERT_STATUS_FAILED = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailed', + { + defaultMessage: 'Failed to close 2 alerts', + } +); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts index bf5532af98b10a..f12fd3015925d4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts @@ -41,7 +41,6 @@ export type UpdateAlertsStatus = ({ export interface UpdateAlertStatusActionProps { query?: string; alertIds: string[]; - status: Status; selectedStatus: Status; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 1489fe467f0840..f194b31dc98a21 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -33,6 +33,10 @@ export const setSignalsStatusRoute = (router: IRouter) => { const siemClient = context.securitySolution?.getAppClient(); const siemResponse = buildSiemResponse(response); const validationErrors = setSignalStatusValidateTypeDependents(request.body); + + return response.ok({ body: { updated: 1, total: 2, version_conflicts: 1 } }); + + /* if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } @@ -79,6 +83,7 @@ export const setSignalsStatusRoute = (router: IRouter) => { statusCode: error.statusCode, }); } + */ } ); }; From 4ec2342c868a7315f07bb2450cfde2d8bb7080dc Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Wed, 19 Aug 2020 19:00:09 +0000 Subject: [PATCH 03/16] Don't let the user retry --- .../components/alerts_table/actions.tsx | 7 +-- .../components/alerts_table/index.tsx | 51 +++++-------------- .../components/alerts_table/translations.ts | 37 ++++++++------ 3 files changed, 36 insertions(+), 59 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 015883b441b0b2..c0d79b83ac7799 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -82,12 +82,7 @@ export const updateAlertStatusAction = async ({ // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: alertIds, isDeleted: true }); - onAlertStatusUpdateSuccess( - response.updated, - response.total, - response.version_conflicts, - selectedStatus - ); + onAlertStatusUpdateSuccess(response.updated, response.version_conflicts, selectedStatus); } catch (error) { onAlertStatusUpdateFailure(selectedStatus, error); } finally { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index ba0103200485ba..183023f4bf9cac 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiPanel, EuiLoadingContent } from '@elastic/eui'; +import { EuiPanel, EuiLoadingContent } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { connect, ConnectedProps, useDispatch } from 'react-redux'; import { Dispatch } from 'redux'; -import { FormattedMessage } from 'react-intl'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { Filter, esQuery } from '../../../../../../../src/plugins/data/public'; import { TimelineIdLiteral } from '../../../../common/types/timeline'; @@ -58,7 +57,6 @@ import { AddExceptionModal, AddExceptionModalBaseProps, } from '../../../common/components/exceptions/add_exception_modal'; -import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; interface OwnProps { timelineId: TimelineIdLiteral; @@ -124,9 +122,6 @@ export const AlertsTableComponent: React.FC = ({ const kibana = useKibana(); const [, dispatchToaster] = useStateToaster(); const { addWarning } = useAppToasts(); - const [lastUpdateRequest, setLastUpdateRequest] = useState< - Pick - >(null); const getGlobalQuery = useCallback( (customFilters: Filter[]) => { @@ -188,37 +183,29 @@ export const AlertsTableComponent: React.FC = ({ ); const onAlertStatusUpdateSuccess = useCallback( - (updated: number, total: number, conflicts: number, status: Status) => { + (updated: number, conflicts: number, status: Status) => { let title: string; switch (status) { case 'closed': - title = i18n.CLOSED_ALERT_SUCCESS_TOAST(updated, total); + title = i18n.CLOSED_ALERT_SUCCESS_TOAST(updated); break; case 'open': - title = i18n.OPENED_ALERT_SUCCESS_TOAST(updated, total); + title = i18n.OPENED_ALERT_SUCCESS_TOAST(updated); break; case 'in-progress': - title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(updated, total); + title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(updated); } if (conflicts > 0) { addWarning({ - title: i18n.UPDATE_ALERT_STATUS_FAILED, - text: toMountPoint( - updateAlertStatusActionWrapper(lastUpdateRequest)}> - - - ), - toastLifeTimeMs: 9999999999, + title: i18n.UPDATE_ALERT_STATUS_FAILED(conflicts), + text: i18n.UPDATE_ALERT_STATUS_FAILED_DETAILED(updated, conflicts), }); } else { displaySuccessToast(title, dispatchToaster); } }, - [addWarning, dispatchToaster, lastUpdateRequest, updateAlertStatusActionWrapper] + [addWarning, dispatchToaster] ); const onAlertStatusUpdateFailure = useCallback( @@ -302,37 +289,25 @@ export const AlertsTableComponent: React.FC = ({ { status, selectedStatus }: UpdateAlertsStatusProps ) => { const currentStatusFilter = buildAlertStatusFilter(status); - const updateRequest = { + await updateAlertStatusAction({ query: showClearSelectionAction ? getGlobalQuery(currentStatusFilter)?.filterQuery : undefined, alertIds: Object.keys(selectedEventIds), selectedStatus, - }; - await updateAlertStatusActionWrapper(updateRequest); - refetchQuery(); - }, - [getGlobalQuery, selectedEventIds, showClearSelectionAction, updateAlertStatusActionWrapper] - ); - - const updateAlertStatusActionWrapper: ( - updateRequest: Pick - ) => void = useCallback( - async ( - updateRequest: Pick - ) => { - await updateAlertStatusAction({ - ...updateRequest, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, }); - setLastUpdateRequest(updateRequest); + refetchQuery(); }, [ + getGlobalQuery, + selectedEventIds, setEventsDeletedCallback, setEventsLoadingCallback, + showClearSelectionAction, onAlertStatusUpdateSuccess, onAlertStatusUpdateFailure, ] diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index bd7ec58ca10bd7..5e5b0ff5532955 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -129,27 +129,27 @@ export const ACTION_ADD_ENDPOINT_EXCEPTION = i18n.translate( } ); -export const CLOSED_ALERT_SUCCESS_TOAST = (totalClosed: number, totalAlerts: number) => +export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => i18n.translate('xpack.securitySolution.detectionEngine.alerts.closedAlertSuccessToastMessage', { - values: { totalClosed, totalAlerts }, + values: { totalAlerts }, defaultMessage: - 'Successfully closed {totalClosed}/{totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + 'Successfully closed {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', }); -export const OPENED_ALERT_SUCCESS_TOAST = (totalOpened: number, totalAlerts: number) => +export const OPENED_ALERT_SUCCESS_TOAST = (totalAlerts: number) => i18n.translate('xpack.securitySolution.detectionEngine.alerts.openedAlertSuccessToastMessage', { - values: { totalOpened, totalAlerts }, + values: { totalAlerts }, defaultMessage: - 'Successfully opened {totalOpened}/{totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', + 'Successfully opened {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}}.', }); -export const IN_PROGRESS_ALERT_SUCCESS_TOAST = (totalInProgress: number, totalAlerts: number) => +export const IN_PROGRESS_ALERT_SUCCESS_TOAST = (totalAlerts: number) => i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.inProgressAlertSuccessToastMessage', { - values: { totalInProgress, totalAlerts }, + values: { totalAlerts }, defaultMessage: - 'Successfully marked {totalInProgress}/{totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}} as in progress.', + 'Successfully marked {totalAlerts} {totalAlerts, plural, =1 {alert} other {alerts}} as in progress.', } ); @@ -174,9 +174,16 @@ export const IN_PROGRESS_ALERT_FAILED_TOAST = i18n.translate( } ); -export const UPDATE_ALERT_STATUS_FAILED = i18n.translate( - 'xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailed', - { - defaultMessage: 'Failed to close 2 alerts', - } -); +export const UPDATE_ALERT_STATUS_FAILED = (conflicts: number) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailed', { + values: { conflicts }, + defaultMessage: + 'Failed to update { conflicts } {conflicts, plural, =1 {alert} other {alerts}}.', + }); + +export const UPDATE_ALERT_STATUS_FAILED_DETAILED = (updated: number, conflicts: number) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailedDetailed', { + values: { updated, conflicts }, + defaultMessage: `{ updated } {updated, plural, =1 {alert was} other {alerts were}} updated successfully, but { conflicts } failed to update + because { conflicts, plural, =1 {it was} other {they were}} already being modified.`, + }); From 832d7cdb5cd31bacd53e131ef855bd679bc48db1 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Wed, 19 Aug 2020 19:31:09 +0000 Subject: [PATCH 04/16] Tweak error messages --- .../components/alerts_table/actions.tsx | 12 +++++++++ .../components/alerts_table/index.tsx | 25 +++++++++---------- .../components/alerts_table/types.ts | 7 +----- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index c0d79b83ac7799..321379aeeb1ede 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -9,6 +9,7 @@ import dateMath from '@elastic/datemath'; import { get, getOr, isEmpty, find } from 'lodash/fp'; import moment from 'moment'; +import { i18n } from '@kbn/i18n'; import { updateAlertStatus } from '../../containers/detection_engine/alerts/api'; import { SendAlertToTimelineActionProps, UpdateAlertStatusActionProps } from './types'; @@ -82,6 +83,17 @@ export const updateAlertStatusAction = async ({ // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: alertIds, isDeleted: true }); + if (response.version_conflicts > 0 && alertIds.length === 1) { + throw new Error( + i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailedSingleAlert', + { + defaultMessage: 'Failed to update alert because it was already being modified.', + } + ) + ); + } + onAlertStatusUpdateSuccess(response.updated, response.version_conflicts, selectedStatus); } catch (error) { onAlertStatusUpdateFailure(selectedStatus, error); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 183023f4bf9cac..a9341654b84371 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -44,7 +44,6 @@ import { SetEventsLoadingProps, UpdateAlertsStatusCallback, UpdateAlertsStatusProps, - UpdateAlertStatusActionProps, } from './types'; import { dispatchUpdateTimeline } from '../../../timelines/components/open_timeline/helpers'; import { @@ -184,24 +183,24 @@ export const AlertsTableComponent: React.FC = ({ const onAlertStatusUpdateSuccess = useCallback( (updated: number, conflicts: number, status: Status) => { - let title: string; - switch (status) { - case 'closed': - title = i18n.CLOSED_ALERT_SUCCESS_TOAST(updated); - break; - case 'open': - title = i18n.OPENED_ALERT_SUCCESS_TOAST(updated); - break; - case 'in-progress': - title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(updated); - } - if (conflicts > 0) { + // Partial failure addWarning({ title: i18n.UPDATE_ALERT_STATUS_FAILED(conflicts), text: i18n.UPDATE_ALERT_STATUS_FAILED_DETAILED(updated, conflicts), }); } else { + let title: string; + switch (status) { + case 'closed': + title = i18n.CLOSED_ALERT_SUCCESS_TOAST(updated); + break; + case 'open': + title = i18n.OPENED_ALERT_SUCCESS_TOAST(updated); + break; + case 'in-progress': + title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(updated); + } displaySuccessToast(title, dispatchToaster); } }, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts index f12fd3015925d4..f8b3cd6af8b8a2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts @@ -44,12 +44,7 @@ export interface UpdateAlertStatusActionProps { selectedStatus: Status; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; - onAlertStatusUpdateSuccess: ( - count: number, - total: number, - conflicts: number, - status: Status - ) => void; + onAlertStatusUpdateSuccess: (updated: number, conflicts: number, status: Status) => void; onAlertStatusUpdateFailure: (status: Status, error: Error) => void; } From 32cae9018d1f39f4a07acf6a7e33047e626356da Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Wed, 19 Aug 2020 19:34:33 +0000 Subject: [PATCH 05/16] Fix route --- .../routes/signals/open_close_signals_route.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index f194b31dc98a21..4459c86f252232 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -34,9 +34,6 @@ export const setSignalsStatusRoute = (router: IRouter) => { const siemResponse = buildSiemResponse(response); const validationErrors = setSignalStatusValidateTypeDependents(request.body); - return response.ok({ body: { updated: 1, total: 2, version_conflicts: 1 } }); - - /* if (validationErrors.length) { return siemResponse.error({ statusCode: 400, body: validationErrors }); } @@ -83,7 +80,6 @@ export const setSignalsStatusRoute = (router: IRouter) => { statusCode: error.statusCode, }); } - */ } ); }; From 4a0f66d6b42ce188783b139991eae54a3dc9caa7 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Wed, 19 Aug 2020 20:28:28 +0000 Subject: [PATCH 06/16] Update add exception modal --- .../exceptions/add_exception_modal/index.tsx | 20 ++++++++++++++----- .../exceptions/use_add_exception.tsx | 11 +++++++--- .../public/common/translations.ts | 14 +++++++++++++ .../components/alerts_table/index.tsx | 5 +++-- .../components/alerts_table/translations.ts | 14 ------------- 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index 03051ead357c96..20f3634d8171cb 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -27,6 +27,7 @@ import { CreateExceptionListItemSchema, ExceptionListType, } from '../../../../../public/lists_plugin_deps'; +import * as i18nCommon from '../../../translations'; import * as i18n from './translations'; import { TimelineNonEcsData, Ecs } from '../../../../graphql/types'; import { useAppToasts } from '../../../hooks/use_app_toasts'; @@ -114,7 +115,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ Array >([]); const [fetchOrCreateListError, setFetchOrCreateListError] = useState(false); - const { addError, addSuccess } = useAppToasts(); + const { addError, addSuccess, addWarning } = useAppToasts(); const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); const [ { isLoading: isSignalIndexPatternLoading, indexPatterns: signalIndexPatterns }, @@ -132,10 +133,19 @@ export const AddExceptionModal = memo(function AddExceptionModal({ }, [addError, onCancel] ); - const onSuccess = useCallback(() => { - addSuccess(i18n.ADD_EXCEPTION_SUCCESS); - onConfirm(shouldCloseAlert); - }, [addSuccess, onConfirm, shouldCloseAlert]); + const onSuccess = useCallback( + (updated: number, conflicts: number) => { + addSuccess(i18n.ADD_EXCEPTION_SUCCESS); + onConfirm(shouldCloseAlert); + if (conflicts > 0) { + addWarning({ + title: i18nCommon.UPDATE_ALERT_STATUS_FAILED(conflicts), + text: i18nCommon.UPDATE_ALERT_STATUS_FAILED_DETAILED(updated, conflicts), + }); + } + }, + [addSuccess, addWarning, onConfirm, shouldCloseAlert] + ); const [{ isLoading: addExceptionIsLoading }, addOrUpdateExceptionItems] = useAddOrUpdateException( { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx index 9d45a411b51302..e424ab1d161f12 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx @@ -43,7 +43,7 @@ export type ReturnUseAddOrUpdateException = [ export interface UseAddOrUpdateExceptionProps { http: HttpStart; onError: (arg: Error) => void; - onSuccess: () => void; + onSuccess: (updated: number, conficts: number) => void; } /** @@ -130,6 +130,8 @@ export const useAddOrUpdateException = ({ }); } + let conflicts = 0; + let updated = 0; if (bulkCloseIndex != null) { const filter = getQueryFilter( '', @@ -139,20 +141,23 @@ export const useAddOrUpdateException = ({ prepareExceptionItemsForBulkClose(exceptionItemsToAddOrUpdate), false ); - await updateAlertStatus({ + + const response = await updateAlertStatus({ query: { query: filter, }, status: 'closed', signal: abortCtrl.signal, }); + conflicts = response.version_conflicts; + updated = response.updated; } await addOrUpdateItems(exceptionItemsToAddOrUpdate); if (isSubscribed) { setIsLoading(false); - onSuccess(); + onSuccess(updated, conflicts); } } catch (error) { if (isSubscribed) { diff --git a/x-pack/plugins/security_solution/public/common/translations.ts b/x-pack/plugins/security_solution/public/common/translations.ts index 3b94ac8959496f..05ebcf6133c739 100644 --- a/x-pack/plugins/security_solution/public/common/translations.ts +++ b/x-pack/plugins/security_solution/public/common/translations.ts @@ -61,3 +61,17 @@ export const EMPTY_ACTION_ENDPOINT_DESCRIPTION = i18n.translate( 'Protect your hosts with threat prevention, detection, and deep security data visibility.', } ); + +export const UPDATE_ALERT_STATUS_FAILED = (conflicts: number) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailed', { + values: { conflicts }, + defaultMessage: + 'Failed to update { conflicts } {conflicts, plural, =1 {alert} other {alerts}}.', + }); + +export const UPDATE_ALERT_STATUS_FAILED_DETAILED = (updated: number, conflicts: number) => + i18n.translate('xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailedDetailed', { + values: { updated, conflicts }, + defaultMessage: `{ updated } {updated, plural, =1 {alert was} other {alerts were}} updated successfully, but { conflicts } failed to update + because { conflicts, plural, =1 {it was} other {they were}} already being modified.`, + }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index a9341654b84371..33cd567c4a4857 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -37,6 +37,7 @@ import { } from './default_config'; import { FILTER_OPEN, AlertsTableFilterGroup } from './alerts_filter_group'; import { AlertsUtilityBar } from './alerts_utility_bar'; +import * as i18nCommon from '../../../common/translations'; import * as i18n from './translations'; import { CreateTimelineProps, @@ -186,8 +187,8 @@ export const AlertsTableComponent: React.FC = ({ if (conflicts > 0) { // Partial failure addWarning({ - title: i18n.UPDATE_ALERT_STATUS_FAILED(conflicts), - text: i18n.UPDATE_ALERT_STATUS_FAILED_DETAILED(updated, conflicts), + title: i18nCommon.UPDATE_ALERT_STATUS_FAILED(conflicts), + text: i18nCommon.UPDATE_ALERT_STATUS_FAILED_DETAILED(updated, conflicts), }); } else { let title: string; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index 5e5b0ff5532955..3d6c3dc0a7a8e5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -173,17 +173,3 @@ export const IN_PROGRESS_ALERT_FAILED_TOAST = i18n.translate( defaultMessage: 'Failed to mark alert(s) as in progress', } ); - -export const UPDATE_ALERT_STATUS_FAILED = (conflicts: number) => - i18n.translate('xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailed', { - values: { conflicts }, - defaultMessage: - 'Failed to update { conflicts } {conflicts, plural, =1 {alert} other {alerts}}.', - }); - -export const UPDATE_ALERT_STATUS_FAILED_DETAILED = (updated: number, conflicts: number) => - i18n.translate('xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailedDetailed', { - values: { updated, conflicts }, - defaultMessage: `{ updated } {updated, plural, =1 {alert was} other {alerts were}} updated successfully, but { conflicts } failed to update - because { conflicts, plural, =1 {it was} other {they were}} already being modified.`, - }); From 7e7d9c34e04be4bfa784527a0748f0a532f75a82 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Tue, 1 Sep 2020 20:03:18 +0000 Subject: [PATCH 07/16] Reapply changes after fixing conflicts --- .../detections/components/alerts_table/actions.tsx | 8 ++++---- .../components/alerts_table/default_config.tsx | 11 ++++------- .../detections/components/alerts_table/index.tsx | 7 ++----- .../detections/components/alerts_table/types.ts | 2 +- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index 321379aeeb1ede..a391e1d33938f5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -68,7 +68,7 @@ export const getFilterAndRuleBounds = ( export const updateAlertStatusAction = async ({ query, alertIds, - selectedStatus, + status, setEventsLoading, setEventsDeleted, onAlertStatusUpdateSuccess, @@ -79,7 +79,7 @@ export const updateAlertStatusAction = async ({ const queryObject = query ? { query: JSON.parse(query) } : getUpdateAlertsQuery(alertIds); - const response = await updateAlertStatus({ query: queryObject, status: selectedStatus }); + const response = await updateAlertStatus({ query: queryObject, status }); // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: alertIds, isDeleted: true }); @@ -94,9 +94,9 @@ export const updateAlertStatusAction = async ({ ); } - onAlertStatusUpdateSuccess(response.updated, response.version_conflicts, selectedStatus); + onAlertStatusUpdateSuccess(response.updated, response.version_conflicts, status); } catch (error) { - onAlertStatusUpdateFailure(selectedStatus, error); + onAlertStatusUpdateFailure(status, error); } finally { setEventsLoading({ eventIds: alertIds, isLoading: false }); } diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index ca17d331c67e58..891e9a12746553 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -218,7 +218,7 @@ interface AlertActionArgs { nonEcsRowData: TimelineNonEcsData[]; hasIndexWrite: boolean; onAlertStatusUpdateFailure: (status: Status, error: Error) => void; - onAlertStatusUpdateSuccess: (count: number, status: Status) => void; + onAlertStatusUpdateSuccess: (updated: number, conflicts: number, status: Status) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; status: Status; @@ -263,8 +263,7 @@ export const getAlertActions = ({ onAlertStatusUpdateSuccess, setEventsDeleted, setEventsLoading, - status, - selectedStatus: FILTER_OPEN, + status: FILTER_OPEN, }), width: DEFAULT_ICON_BUTTON_WIDTH, }; @@ -283,8 +282,7 @@ export const getAlertActions = ({ onAlertStatusUpdateSuccess, setEventsDeleted, setEventsLoading, - status, - selectedStatus: FILTER_CLOSED, + status: FILTER_CLOSED, }), width: DEFAULT_ICON_BUTTON_WIDTH, }; @@ -303,8 +301,7 @@ export const getAlertActions = ({ onAlertStatusUpdateSuccess, setEventsDeleted, setEventsLoading, - status, - selectedStatus: FILTER_IN_PROGRESS, + status: FILTER_IN_PROGRESS, }), width: DEFAULT_ICON_BUTTON_WIDTH, }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 9bb1709d9a583b..7f5c5326d39c94 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -297,17 +297,14 @@ export const AlertsTableComponent: React.FC = ({ }, [setSelectAll, setShowClearSelectionAction, timelineId]); const updateAlertsStatusCallback: UpdateAlertsStatusCallback = useCallback( - async ( - refetchQuery: inputsModel.Refetch, - { status, selectedStatus }: UpdateAlertsStatusProps - ) => { + async (refetchQuery: inputsModel.Refetch, { status }: UpdateAlertsStatusProps) => { const currentStatusFilter = buildAlertStatusFilter(status); await updateAlertStatusAction({ query: showClearSelectionAction ? getGlobalQuery(currentStatusFilter)?.filterQuery : undefined, alertIds: Object.keys(selectedEventIds), - selectedStatus, + status, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, onAlertStatusUpdateSuccess, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts index f8b3cd6af8b8a2..a1bfb9beecf73b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts @@ -41,7 +41,7 @@ export type UpdateAlertsStatus = ({ export interface UpdateAlertStatusActionProps { query?: string; alertIds: string[]; - selectedStatus: Status; + status: Status; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; onAlertStatusUpdateSuccess: (updated: number, conflicts: number, status: Status) => void; From a1447823b23cce717d37421aab5ed264b41e26be Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Wed, 2 Sep 2020 01:57:09 +0000 Subject: [PATCH 08/16] Type errors --- .../detections/components/alerts_table/actions.tsx | 7 ++++--- .../components/alerts_table/default_config.tsx | 9 ++++++--- .../public/detections/components/alerts_table/index.tsx | 6 +++++- .../public/detections/components/alerts_table/types.ts | 1 + 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx index a391e1d33938f5..ff7f6104912968 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx @@ -69,6 +69,7 @@ export const updateAlertStatusAction = async ({ query, alertIds, status, + selectedStatus, setEventsLoading, setEventsDeleted, onAlertStatusUpdateSuccess, @@ -79,7 +80,7 @@ export const updateAlertStatusAction = async ({ const queryObject = query ? { query: JSON.parse(query) } : getUpdateAlertsQuery(alertIds); - const response = await updateAlertStatus({ query: queryObject, status }); + const response = await updateAlertStatus({ query: queryObject, status: selectedStatus }); // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: alertIds, isDeleted: true }); @@ -94,9 +95,9 @@ export const updateAlertStatusAction = async ({ ); } - onAlertStatusUpdateSuccess(response.updated, response.version_conflicts, status); + onAlertStatusUpdateSuccess(response.updated, response.version_conflicts, selectedStatus); } catch (error) { - onAlertStatusUpdateFailure(status, error); + onAlertStatusUpdateFailure(selectedStatus, error); } finally { setEventsLoading({ eventIds: alertIds, isLoading: false }); } diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 891e9a12746553..6357168d8cb9ad 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -263,7 +263,8 @@ export const getAlertActions = ({ onAlertStatusUpdateSuccess, setEventsDeleted, setEventsLoading, - status: FILTER_OPEN, + status, + selectedStatus: FILTER_OPEN, }), width: DEFAULT_ICON_BUTTON_WIDTH, }; @@ -282,7 +283,8 @@ export const getAlertActions = ({ onAlertStatusUpdateSuccess, setEventsDeleted, setEventsLoading, - status: FILTER_CLOSED, + status, + selectedStatus: FILTER_CLOSED, }), width: DEFAULT_ICON_BUTTON_WIDTH, }; @@ -301,7 +303,8 @@ export const getAlertActions = ({ onAlertStatusUpdateSuccess, setEventsDeleted, setEventsLoading, - status: FILTER_IN_PROGRESS, + status, + selectedStatus: FILTER_IN_PROGRESS, }), width: DEFAULT_ICON_BUTTON_WIDTH, }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index 7f5c5326d39c94..e9920cb3957a70 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -297,7 +297,10 @@ export const AlertsTableComponent: React.FC = ({ }, [setSelectAll, setShowClearSelectionAction, timelineId]); const updateAlertsStatusCallback: UpdateAlertsStatusCallback = useCallback( - async (refetchQuery: inputsModel.Refetch, { status }: UpdateAlertsStatusProps) => { + async ( + refetchQuery: inputsModel.Refetch, + { status, selectedStatus }: UpdateAlertsStatusProps + ) => { const currentStatusFilter = buildAlertStatusFilter(status); await updateAlertStatusAction({ query: showClearSelectionAction @@ -305,6 +308,7 @@ export const AlertsTableComponent: React.FC = ({ : undefined, alertIds: Object.keys(selectedEventIds), status, + selectedStatus, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, onAlertStatusUpdateSuccess, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts index a1bfb9beecf73b..a4822409166a2d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts @@ -41,6 +41,7 @@ export type UpdateAlertsStatus = ({ export interface UpdateAlertStatusActionProps { query?: string; alertIds: string[]; + selectedStatus: Status; status: Status; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; From 8cd9c9723cbe81af98f3eddf9e1ff53f86891a6d Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Wed, 2 Sep 2020 19:24:46 +0000 Subject: [PATCH 09/16] types --- .../public/detections/components/alerts_table/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts index a4822409166a2d..f8b3cd6af8b8a2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/types.ts @@ -42,7 +42,6 @@ export interface UpdateAlertStatusActionProps { query?: string; alertIds: string[]; selectedStatus: Status; - status: Status; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; onAlertStatusUpdateSuccess: (updated: number, conflicts: number, status: Status) => void; From 46a18e693dde9fdf80be3295597a47811d42d253 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Thu, 3 Sep 2020 02:56:46 +0000 Subject: [PATCH 10/16] Fix remaining conflicts --- .../timeline_actions/alert_context_menu.tsx | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 589116c901c303..af9d95aae1faee 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -15,6 +15,7 @@ import { } from '@elastic/eui'; import styled from 'styled-components'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { TimelineId } from '../../../../../common/types/timeline'; import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants'; import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; @@ -33,6 +34,7 @@ import { AddExceptionModalBaseProps, } from '../../../../common/components/exceptions/add_exception_modal'; import { getMappedNonEcsValue } from '../../../../common/components/exceptions/helpers'; +import * as i18nCommon from '../../../../common/translations'; import * as i18n from '../translations'; import { useStateToaster, @@ -73,6 +75,8 @@ const AlertContextMenuComponent: React.FC = ({ ); const eventId = ecsRowData._id; + const { addWarning } = useAppToasts(); + const onButtonClick = useCallback(() => { setPopover(!isPopoverOpen); }, [isPopoverOpen]); @@ -125,22 +129,30 @@ const AlertContextMenuComponent: React.FC = ({ ); const onAlertStatusUpdateSuccess = useCallback( - (count: number, newStatus: Status) => { - let title: string; - switch (newStatus) { - case 'closed': - title = i18n.CLOSED_ALERT_SUCCESS_TOAST(count); - break; - case 'open': - title = i18n.OPENED_ALERT_SUCCESS_TOAST(count); - break; - case 'in-progress': - title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(count); + (updated: number, conflicts: number, newStatus: Status) => { + if (conflicts > 0) { + // Partial failure + addWarning({ + title: i18nCommon.UPDATE_ALERT_STATUS_FAILED(conflicts), + text: i18nCommon.UPDATE_ALERT_STATUS_FAILED_DETAILED(updated, conflicts), + }); + } else { + let title: string; + switch (newStatus) { + case 'closed': + title = i18n.CLOSED_ALERT_SUCCESS_TOAST(updated); + break; + case 'open': + title = i18n.OPENED_ALERT_SUCCESS_TOAST(updated); + break; + case 'in-progress': + title = i18n.IN_PROGRESS_ALERT_SUCCESS_TOAST(updated); + } + displaySuccessToast(title, dispatchToaster); } - displaySuccessToast(title, dispatchToaster); setAlertStatus(newStatus); }, - [dispatchToaster] + [dispatchToaster, addWarning] ); const onAlertStatusUpdateFailure = useCallback( From f0ee82b003596f7803ec6314282d45f3437882a1 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Thu, 3 Sep 2020 15:25:44 +0000 Subject: [PATCH 11/16] Fix tests --- .../public/common/hooks/use_app_toasts.test.ts | 3 +++ .../detections/containers/detection_engine/alerts/api.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts index e0e629793952a7..da43d0c5109974 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts @@ -14,13 +14,16 @@ jest.mock('../lib/kibana'); describe('useDeleteList', () => { let addErrorMock: jest.Mock; let addSuccessMock: jest.Mock; + let addWarningMock: jest.Mock; beforeEach(() => { addErrorMock = jest.fn(); addSuccessMock = jest.fn(); + addWarningMock = jest.fn(); (useToasts as jest.Mock).mockImplementation(() => ({ addError: addErrorMock, addSuccess: addSuccessMock, + addWarning: addWarningMock, })); }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts index 3cd819b55685c0..19007c4d2e432b 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts @@ -67,7 +67,7 @@ describe('Detections Alerts API', () => { }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { body: - '{"status":"closed","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', + '{"conflicts":"proceed","status":"closed","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', method: 'POST', signal: abortCtrl.signal, }); @@ -81,7 +81,7 @@ describe('Detections Alerts API', () => { }); expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/signals/status', { body: - '{"status":"open","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', + '{"conflicts":"proceed","status":"open","bool":{"filter":{"terms":{"_id":["b4ee5c32e3a321057edcc953ca17228c6fdfe5ba43fdbbdaffa8cefa11605cc5"]}}}}', method: 'POST', signal: abortCtrl.signal, }); From 0b391641d3ef978df0b91cec88d0691ca6f04dcb Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Thu, 3 Sep 2020 19:46:21 +0000 Subject: [PATCH 12/16] More test fixes --- .../exceptions/use_add_exception.test.tsx | 2 +- .../exceptions/use_add_exception.tsx | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx index 46923e07d225ad..2398f8d799c2ce 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.test.tsx @@ -49,7 +49,7 @@ describe('useAddOrUpdateException', () => { const onError = jest.fn(); const onSuccess = jest.fn(); const alertIdToClose = 'idToClose'; - const bulkCloseIndex = ['.signals']; + const bulkCloseIndex = ['.custom']; const itemsToAdd: CreateExceptionListItemSchema[] = [ { ...getCreateExceptionListItemSchemaMock(), diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx index 8007284a4f7348..dbd634e97a3281 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/use_add_exception.tsx @@ -5,6 +5,7 @@ */ import { useEffect, useRef, useState, useCallback } from 'react'; +import { UpdateDocumentByQueryResponse } from 'elasticsearch'; import { HttpStart } from '../../../../../../../src/core/public'; import { @@ -122,16 +123,16 @@ export const useAddOrUpdateException = ({ ) => { try { setIsLoading(true); - if (alertIdToClose !== null && alertIdToClose !== undefined) { - await updateAlertStatus({ + let alertIdResponse: UpdateDocumentByQueryResponse | undefined; + let bulkResponse: UpdateDocumentByQueryResponse | undefined; + if (alertIdToClose != null) { + alertIdResponse = await updateAlertStatus({ query: getUpdateAlertsQuery([alertIdToClose]), status: 'closed', signal: abortCtrl.signal, }); } - let conflicts = 0; - let updated = 0; if (bulkCloseIndex != null) { const filter = getQueryFilter( '', @@ -142,19 +143,26 @@ export const useAddOrUpdateException = ({ false ); - const response = await updateAlertStatus({ + bulkResponse = await updateAlertStatus({ query: { query: filter, }, status: 'closed', signal: abortCtrl.signal, }); - conflicts = response.version_conflicts; - updated = response.updated; } await addOrUpdateItems(exceptionItemsToAddOrUpdate); + // NOTE: there could be some overlap here... it's possible that the first response had conflicts + // but that the alert was closed in the second call. In this case, a conflict will be reported even + // though it was already resolved. I'm not sure that there's an easy way to solve this, but it should + // have minimal impact on the user... they'd see a warning that indicates a possible conflict, but the + // state of the alerts and their representation in the UI would be consistent. + const updated = (alertIdResponse?.updated ?? 0) + (bulkResponse?.updated ?? 0); + const conflicts = + alertIdResponse?.version_conflicts ?? 0 + (bulkResponse?.version_conflicts ?? 0); + if (isSubscribed) { setIsLoading(false); onSuccess(updated, conflicts); From 09815192babe673a5182a1d3f32ec037185a6f80 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Thu, 3 Sep 2020 19:48:35 +0000 Subject: [PATCH 13/16] Simplify onConflict evaluation --- .../routes/signals/open_close_signals_route.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 4459c86f252232..be6e57aee6d0cf 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -53,14 +53,10 @@ export const setSignalsStatusRoute = (router: IRouter) => { }, }; } - let onConflict = 'abort'; - if (conflicts != null) { - onConflict = conflicts; - } try { const result = await clusterClient.callAsCurrentUser('updateByQuery', { index: siemClient.getSignalsIndex(), - conflicts: onConflict, + conflicts: conflicts ?? 'abort', refresh: 'wait_for', body: { script: { From 3ad559d0595a6eaa3c139388543b02dec9839757 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Thu, 3 Sep 2020 20:02:08 +0000 Subject: [PATCH 14/16] Add callback return types --- .../exceptions/add_exception_modal/index.tsx | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index c292f42ff67001..c34080de0e8871 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -130,7 +130,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ); const onError = useCallback( - (error: Error) => { + (error: Error): void => { addError(error, { title: i18n.ADD_EXCEPTION_ERROR }); onCancel(); }, @@ -138,7 +138,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ); const onSuccess = useCallback( - (updated: number, conflicts: number) => { + (updated: number, conflicts: number): void => { addSuccess(i18n.ADD_EXCEPTION_SUCCESS); onConfirm(shouldCloseAlert, shouldBulkCloseAlert); if (conflicts > 0) { @@ -164,7 +164,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ exceptionItems, }: { exceptionItems: Array; - }) => { + }): void => { setExceptionItemsToAdd(exceptionItems); }, [setExceptionItemsToAdd] @@ -197,7 +197,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ); const handleFetchOrCreateExceptionListError = useCallback( - (error: Error, statusCode: number | null, message: string | null) => { + (error: Error, statusCode: number | null, message: string | null): void => { setFetchOrCreateListError({ reason: error.message, code: statusCode, @@ -216,7 +216,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ onSuccess: handleRuleChange, }); - const initialExceptionItems = useMemo(() => { + const initialExceptionItems = useMemo((): ExceptionsBuilderExceptionItem[] => { if (exceptionListType === 'endpoint' && alertData !== undefined && ruleExceptionList) { return defaultEndpointExceptionItems( exceptionListType, @@ -229,7 +229,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ } }, [alertData, exceptionListType, ruleExceptionList, ruleName]); - useEffect(() => { + useEffect((): void => { if (isSignalIndexPatternLoading === false && isSignalIndexLoading === false) { setShouldDisableBulkClose( entryHasListType(exceptionItemsToAdd) || @@ -245,34 +245,34 @@ export const AddExceptionModal = memo(function AddExceptionModal({ signalIndexPatterns, ]); - useEffect(() => { + useEffect((): void => { if (shouldDisableBulkClose === true) { setShouldBulkCloseAlert(false); } }, [shouldDisableBulkClose]); const onCommentChange = useCallback( - (value: string) => { + (value: string): void => { setComment(value); }, [setComment] ); const onCloseAlertCheckboxChange = useCallback( - (event: React.ChangeEvent) => { + (event: React.ChangeEvent): void => { setShouldCloseAlert(event.currentTarget.checked); }, [setShouldCloseAlert] ); const onBulkCloseAlertCheckboxChange = useCallback( - (event: React.ChangeEvent) => { + (event: React.ChangeEvent): void => { setShouldBulkCloseAlert(event.currentTarget.checked); }, [setShouldBulkCloseAlert] ); - const retrieveAlertOsTypes = useCallback(() => { + const retrieveAlertOsTypes = useCallback((): string[] => { const osDefaults = ['windows', 'macos']; if (alertData) { const osTypes = getMappedNonEcsValue({ @@ -287,7 +287,9 @@ export const AddExceptionModal = memo(function AddExceptionModal({ return osDefaults; }, [alertData]); - const enrichExceptionItems = useCallback(() => { + const enrichExceptionItems = useCallback((): Array< + ExceptionListItemSchema | CreateExceptionListItemSchema + > => { let enriched: Array = []; enriched = comment !== '' @@ -300,7 +302,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ return enriched; }, [comment, exceptionItemsToAdd, exceptionListType, retrieveAlertOsTypes]); - const onAddExceptionConfirm = useCallback(() => { + const onAddExceptionConfirm = useCallback((): void => { if (addOrUpdateExceptionItems !== null) { const alertIdToClose = shouldCloseAlert && alertData ? alertData.ecsData._id : undefined; const bulkCloseIndex = @@ -317,7 +319,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ]); const isSubmitButtonDisabled = useMemo( - () => + (): boolean => fetchOrCreateListError != null || exceptionItemsToAdd.every((item) => item.entries.length === 0), [fetchOrCreateListError, exceptionItemsToAdd] From 7297fc21b8462453677becb7eadeff0d52154245 Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Thu, 3 Sep 2020 20:05:28 +0000 Subject: [PATCH 15/16] Update translation paths --- .../plugins/security_solution/public/common/translations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/translations.ts b/x-pack/plugins/security_solution/public/common/translations.ts index 05ebcf6133c739..c4a9540f629142 100644 --- a/x-pack/plugins/security_solution/public/common/translations.ts +++ b/x-pack/plugins/security_solution/public/common/translations.ts @@ -63,14 +63,14 @@ export const EMPTY_ACTION_ENDPOINT_DESCRIPTION = i18n.translate( ); export const UPDATE_ALERT_STATUS_FAILED = (conflicts: number) => - i18n.translate('xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailed', { + i18n.translate('xpack.securitySolution.pages.common.updateAlertStatusFailed', { values: { conflicts }, defaultMessage: 'Failed to update { conflicts } {conflicts, plural, =1 {alert} other {alerts}}.', }); export const UPDATE_ALERT_STATUS_FAILED_DETAILED = (updated: number, conflicts: number) => - i18n.translate('xpack.securitySolution.detectionEngine.alerts.updateAlertStatusFailedDetailed', { + i18n.translate('xpack.securitySolution.pages.common.updateAlertStatusFailedDetailed', { values: { updated, conflicts }, defaultMessage: `{ updated } {updated, plural, =1 {alert was} other {alerts were}} updated successfully, but { conflicts } failed to update because { conflicts, plural, =1 {it was} other {they were}} already being modified.`, From 270fe812a2c2db8a219410e7f0c2658ff78ac1fa Mon Sep 17 00:00:00 2001 From: Madison Caldwell Date: Fri, 4 Sep 2020 15:09:25 +0000 Subject: [PATCH 16/16] Add missing import --- .../common/components/exceptions/add_exception_modal/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index c34080de0e8871..c1befabdd78095 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -50,6 +50,7 @@ import { } from '../helpers'; import { ErrorInfo, ErrorCallout } from '../error_callout'; import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules'; +import { ExceptionsBuilderExceptionItem } from '../types'; export interface AddExceptionModalBaseProps { ruleName: string;