diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/index.tsx b/x-pack/plugins/security_solution/public/common/components/toasters/index.tsx index ea17b03082751..b9dd782d8a653 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/toasters/index.tsx @@ -16,34 +16,58 @@ import * as i18n from './translations'; export * from './utils'; export * from './errors'; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export interface AppToast extends Toast { errors?: string[]; } +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ interface ToastState { toasts: AppToast[]; } +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ const initialToasterState: ToastState = { toasts: [], }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export type ActionToaster = | { type: 'addToaster'; toast: AppToast } | { type: 'deleteToaster'; id: string } | { type: 'toggleWaitToShowNextToast' }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export const StateToasterContext = createContext<[ToastState, Dispatch]>([ initialToasterState, () => noop, ]); +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export const useStateToaster = () => useContext(StateToasterContext); +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ interface ManageGlobalToasterProps { children: React.ReactNode; } +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export const ManageGlobalToaster = ({ children }: ManageGlobalToasterProps) => { const reducerToaster = (state: ToastState, action: ActionToaster) => { switch (action.type) { @@ -63,16 +87,25 @@ export const ManageGlobalToaster = ({ children }: ManageGlobalToasterProps) => { ); }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ const GlobalToasterListContainer = styled.div` position: absolute; right: 0; bottom: 0; `; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ interface GlobalToasterProps { toastLifeTimeMs?: number; } +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export const GlobalToaster = ({ toastLifeTimeMs = 5000 }: GlobalToasterProps) => { const [{ toasts }, dispatch] = useStateToaster(); const [isShowing, setIsShowing] = useState(false); @@ -108,6 +141,9 @@ export const GlobalToaster = ({ toastLifeTimeMs = 5000 }: GlobalToasterProps) => ); }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ const formatToErrorToastIfNeeded = ( toast: AppToast, toggle: (toast: AppToast) => void @@ -129,8 +165,14 @@ const formatToErrorToastIfNeeded = ( return toast; }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ const ErrorToastContainer = styled.div` text-align: right; `; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ ErrorToastContainer.displayName = 'ErrorToastContainer'; diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts index 9ce8ec0cb6fd3..70e095c88576f 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts @@ -15,7 +15,7 @@ import { isAppError } from '../../utils/api'; /** * Displays an error toast for the provided title and message - * + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead * @param errorTitle Title of error to display in toaster and modal * @param errorMessages Message to display in error modal when clicked * @param dispatchToaster provided by useStateToaster() @@ -41,7 +41,7 @@ export const displayErrorToast = ( /** * Displays a warning toast for the provided title and message - * + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead * @param title warning message to display in toaster and modal * @param dispatchToaster provided by useStateToaster() * @param id unique ID if necessary @@ -65,7 +65,7 @@ export const displayWarningToast = ( /** * Displays a success toast for the provided title and message - * + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead * @param title success message to display in toaster and modal * @param dispatchToaster provided by useStateToaster() */ @@ -92,8 +92,16 @@ export type ErrorToToasterArgs = Partial & { }; /** - * Displays an error toast with messages parsed from the error + * Displays an error toast with messages parsed from the error. + * + * This has shortcomings and bugs compared to using the use_app_toasts because it takes naive guesses at the + * underlying data structure and does not display much about the error. This is not compatible with bsearch (async search) + * and sometimes can display to the user blank messages. + * + * The use_app_toasts has more feature rich logic and uses the Kibana toaster system to figure out which type of + * error you have in a more robust way then this function does and supersedes this function. * + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead * @param title error message to display in toaster and modal * @param error the error from which messages will be parsed * @param dispatchToaster provided by useStateToaster() diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.test.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.test.ts index 4f12ec2e5de2d..21791952fec06 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.test.ts @@ -43,6 +43,11 @@ const mockUseKibana = { jest.mock('../../../../common/lib/kibana', () => ({ useKibana: jest.fn(), + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), })); describe('useTimelineLastEventTime', () => { diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts index 0a7df66f6c1d5..3e690e50b04b1 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts @@ -25,6 +25,7 @@ import { } from '../../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; import { DocValueFields } from '../../../../../common/search_strategy'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; export interface UseTimelineLastEventTimeArgs { lastSeen: string | null; @@ -45,7 +46,7 @@ export const useTimelineLastEventTime = ({ indexNames, details, }: UseTimelineLastEventTimeProps): [boolean, UseTimelineLastEventTimeArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -69,6 +70,7 @@ export const useTimelineLastEventTime = ({ refetch: refetch.current, errorMessage: undefined, }); + const { addError, addWarning } = useAppToasts(); const timelineLastEventTimeSearch = useCallback( (request: TimelineEventsLastEventTimeRequestOptions) => { @@ -96,15 +98,13 @@ export const useTimelineLastEventTime = ({ })); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_LAST_EVENT_TIME); + addWarning(i18n.ERROR_LAST_EVENT_TIME); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_LAST_EVENT_TIME, - text: msg.message, }); setTimelineLastEventTimeResponse((prevResponse) => ({ ...prevResponse, @@ -118,7 +118,7 @@ export const useTimelineLastEventTime = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 7884c7bd00263..19c706b86577d 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -24,6 +24,7 @@ import { isErrorResponse, isCompleteResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../hooks/use_app_toasts'; export type Buckets = Array<{ key: string; @@ -60,7 +61,7 @@ export const useMatrixHistogram = ({ UseMatrixHistogramArgs, (to: string, from: string) => void ] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -83,6 +84,7 @@ export const useMatrixHistogram = ({ ...(isPtrIncluded != null ? { isPtrIncluded } : {}), ...(!isEmpty(docValueFields) ? { docValueFields } : {}), }); + const { addError, addWarning } = useAppToasts(); const [matrixHistogramResponse, setMatrixHistogramResponse] = useState({ data: [], @@ -126,14 +128,13 @@ export const useMatrixHistogram = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_MATRIX_HISTOGRAM); + addWarning(i18n.ERROR_MATRIX_HISTOGRAM); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addError(msg, { + addError(msg, { title: errorMessage ?? i18n.FAIL_MATRIX_HISTOGRAM, }); searchSubscription$.current.unsubscribe(); @@ -145,7 +146,7 @@ export const useMatrixHistogram = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, errorMessage, notifications.toasts] + [data.search, errorMessage, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index f66b060b166bf..1c17f95bb6ba0 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -26,6 +26,7 @@ import * as i18n from './translations'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; import { DocValueFields } from '../../../../common/search_strategy/common'; +import { useAppToasts } from '../../hooks/use_app_toasts'; export { BrowserField, BrowserFields, DocValueFields }; @@ -125,7 +126,7 @@ export const useFetchIndex = ( indexNames: string[], onlyCheckIfIndicesExist: boolean = false ): [boolean, FetchIndexReturn] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const previousIndexesName = useRef([]); @@ -138,6 +139,7 @@ export const useFetchIndex = ( indexExists: true, indexPatterns: DEFAULT_INDEX_PATTERNS, }); + const { addError, addWarning } = useAppToasts(); const indexFieldsSearch = useCallback( (iNames) => { @@ -168,14 +170,13 @@ export const useFetchIndex = ( searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS); + addWarning(i18n.ERROR_BEAT_FIELDS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ - text: msg.message, + addError(msg, { title: i18n.FAIL_BEAT_FIELDS, }); searchSubscription$.current.unsubscribe(); @@ -186,7 +187,7 @@ export const useFetchIndex = ( abortCtrl.current.abort(); asyncSearch(); }, - [data.search, notifications.toasts, onlyCheckIfIndicesExist] + [data.search, addError, addWarning, onlyCheckIfIndicesExist] ); useEffect(() => { @@ -203,7 +204,7 @@ export const useFetchIndex = ( }; export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const dispatch = useDispatch(); @@ -215,6 +216,7 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { indexNames: string[]; previousIndexNames: string; }>((state) => indexNamesSelectedSelector(state, sourcererScopeName)); + const { addError, addWarning } = useAppToasts(); const setLoading = useCallback( (loading: boolean) => { @@ -257,14 +259,13 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS); + addWarning(i18n.ERROR_BEAT_FIELDS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ - text: msg.message, + addError(msg, { title: i18n.FAIL_BEAT_FIELDS, }); searchSubscription$.current.unsubscribe(); @@ -275,7 +276,7 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { abortCtrl.current.abort(); asyncSearch(); }, - [data.search, dispatch, notifications.toasts, setLoading, sourcererScopeName] + [data.search, dispatch, addError, addWarning, setLoading, sourcererScopeName] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx index 542369fdf5aa3..702a532949428 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx @@ -51,6 +51,11 @@ jest.mock('../../utils/route/use_route_spy', () => ({ useRouteSpy: () => [mockRouteSpy], })); jest.mock('../../lib/kibana', () => ({ + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), useKibana: jest.fn().mockReturnValue({ services: { application: { diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.test.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.test.ts index 2afe14644f5e9..b1cd14fa039b5 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.test.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.test.ts @@ -17,8 +17,10 @@ import { EqlSearchResponse } from '../../../../common/detection_engine/types'; import { useKibana } from '../../../common/lib/kibana'; import { useEqlPreview } from '.'; import { getMockEqlResponse } from './eql_search_response.mock'; +import { useAppToasts } from '../use_app_toasts'; jest.mock('../../../common/lib/kibana'); +jest.mock('../use_app_toasts'); describe('useEqlPreview', () => { const params = { @@ -29,10 +31,19 @@ describe('useEqlPreview', () => { from: '2020-10-04T15:00:54.368707900Z', }; - beforeEach(() => { - useKibana().services.notifications.toasts.addError = jest.fn(); + let addErrorMock: jest.Mock; + let addSuccessMock: jest.Mock; + let addWarningMock: jest.Mock; - useKibana().services.notifications.toasts.addWarning = jest.fn(); + beforeEach(() => { + addErrorMock = jest.fn(); + addSuccessMock = jest.fn(); + addWarningMock = jest.fn(); + (useAppToasts as jest.Mock).mockImplementation(() => ({ + addError: addErrorMock, + addWarning: addWarningMock, + addSuccess: addSuccessMock, + })); (useKibana().services.data.search.search as jest.Mock).mockReturnValue( of(getMockEqlResponse()) @@ -134,11 +145,8 @@ describe('useEqlPreview', () => { result.current[1](params); - const mockCalls = (useKibana().services.notifications.toasts.addWarning as jest.Mock).mock - .calls; - expect(result.current[0]).toBeFalsy(); - expect(mockCalls[0][0]).toEqual(i18n.EQL_PREVIEW_FETCH_FAILURE); + expect(addWarningMock.mock.calls[0][0]).toEqual(i18n.EQL_PREVIEW_FETCH_FAILURE); }); }); @@ -166,7 +174,7 @@ describe('useEqlPreview', () => { }); }); - it('should add danger toast if search throws', async () => { + it('should add error toast if search throws', async () => { await act(async () => { (useKibana().services.data.search.search as jest.Mock).mockReturnValue( throwError('This is an error!') @@ -178,11 +186,8 @@ describe('useEqlPreview', () => { result.current[1](params); - const mockCalls = (useKibana().services.notifications.toasts.addError as jest.Mock).mock - .calls; - expect(result.current[0]).toBeFalsy(); - expect(mockCalls[0][0]).toEqual('This is an error!'); + expect(addErrorMock.mock.calls[0][0]).toEqual('This is an error!'); }); }); diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts index 5632dd0ed03be..788ce00ba1b1d 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts @@ -27,18 +27,20 @@ import { EqlSearchResponse } from '../../../../common/detection_engine/types'; import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; import { inputsModel } from '../../../common/store'; import { EQL_SEARCH_STRATEGY } from '../../../../../data_enhanced/public'; +import { useAppToasts } from '../use_app_toasts'; export const useEqlPreview = (): [ boolean, (arg: EqlPreviewRequest) => void, EqlPreviewResponse ] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const unsubscribeStream = useRef(new Subject()); const [loading, setLoading] = useState(false); const didCancel = useRef(false); + const { addError, addWarning } = useAppToasts(); const [response, setResponse] = useState({ data: [], @@ -53,7 +55,7 @@ export const useEqlPreview = (): [ const searchEql = useCallback( ({ from, to, query, index, interval }: EqlPreviewRequest) => { if (parseScheduleDates(to) == null || parseScheduleDates(from) == null) { - notifications.toasts.addWarning('Time intervals are not defined.'); + addWarning(i18n.EQL_TIME_INTERVAL_NOT_DEFINED); return; } @@ -138,7 +140,7 @@ export const useEqlPreview = (): [ setResponse((prev) => ({ ...prev, inspect: formatInspect(res, index) })); } else if (isErrorResponse(res)) { setLoading(false); - notifications.toasts.addWarning(i18n.EQL_PREVIEW_FETCH_FAILURE); + addWarning(i18n.EQL_PREVIEW_FETCH_FAILURE); unsubscribeStream.current.next(); } }, @@ -154,7 +156,7 @@ export const useEqlPreview = (): [ refetch: refetch.current, totalCount: 0, }); - notifications.toasts.addError(err, { + addError(err, { title: i18n.EQL_PREVIEW_FETCH_FAILURE, }); } @@ -166,7 +168,7 @@ export const useEqlPreview = (): [ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect((): (() => void) => { diff --git a/x-pack/plugins/security_solution/public/common/hooks/translations.ts b/x-pack/plugins/security_solution/public/common/hooks/translations.ts index 90a848329c013..520cfef74ce41 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/translations.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/translations.ts @@ -46,3 +46,10 @@ export const EQL_PREVIEW_FETCH_FAILURE = i18n.translate( defaultMessage: 'EQL Preview Error', } ); + +export const EQL_TIME_INTERVAL_NOT_DEFINED = i18n.translate( + 'xpack.securitySolution.components.hooks.errors.timeIntervalsNotDefined', + { + defaultMessage: 'Time intervals are not defined.', + } +); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.mock.ts b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.mock.ts index 2b224f1bb6125..25c0f5411f25c 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.mock.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.mock.ts @@ -8,6 +8,7 @@ const createAppToastsMock = () => ({ addError: jest.fn(), addSuccess: jest.fn(), + addWarning: jest.fn(), }); export const useAppToastsMock = { 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 e8a13a1cc183e..27f584bb17248 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 @@ -6,13 +6,14 @@ */ import { renderHook } from '@testing-library/react-hooks'; +import { IEsError } from 'src/plugins/data/public'; import { useToasts } from '../lib/kibana'; import { useAppToasts } from './use_app_toasts'; jest.mock('../lib/kibana'); -describe('useDeleteList', () => { +describe('useAppToasts', () => { let addErrorMock: jest.Mock; let addSuccessMock: jest.Mock; let addWarningMock: jest.Mock; @@ -37,31 +38,36 @@ describe('useDeleteList', () => { expect(addErrorMock).toHaveBeenCalledWith(error, { title: 'title' }); }); - it("uses a AppError's body.message as the toastMessage", async () => { - const kibanaApiError = { - message: 'Not Found', - body: { status_code: 404, message: 'Detailed Message' }, - }; + it('converts an unknown error to an Error', () => { + const unknownError = undefined; const { result } = renderHook(() => useAppToasts()); - result.current.addError(kibanaApiError, { title: 'title' }); + result.current.addError(unknownError, { title: 'title' }); - expect(addErrorMock).toHaveBeenCalledWith(kibanaApiError, { + expect(addErrorMock).toHaveBeenCalledWith(Error(`${undefined}`), { title: 'title', - toastMessage: 'Detailed Message', }); }); - it('converts an unknown error to an Error', () => { - const unknownError = undefined; - + it('works normally with a bsearch type error', async () => { + const error = ({ + message: 'some message', + attributes: {}, + err: { + statusCode: 400, + innerMessages: { somethingElse: 'message' }, + }, + } as unknown) as IEsError; const { result } = renderHook(() => useAppToasts()); - result.current.addError(unknownError, { title: 'title' }); - - expect(addErrorMock).toHaveBeenCalledWith(Error(`${undefined}`), { - title: 'title', + result.current.addError(error, { title: 'title' }); + const errorObj = addErrorMock.mock.calls[0][0]; + expect(errorObj).toEqual({ + message: 'some message (400)', + name: 'some message', + stack: + '{\n "statusCode": 400,\n "innerMessages": {\n "somethingElse": "message"\n }\n}', }); }); }); 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 a797d56835ae7..f5a3c75747e52 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 @@ -6,45 +6,79 @@ */ import { useCallback, useRef } from 'react'; +import { IEsError, isEsError } from '../../../../../../src/plugins/data/public'; import { ErrorToastOptions, ToastsStart, Toast } from '../../../../../../src/core/public'; import { useToasts } from '../lib/kibana'; -import { isAppError, AppError } from '../utils/api'; +import { isAppError } from '../utils/api'; export type UseAppToasts = Pick & { api: ToastsStart; addError: (error: unknown, options: ErrorToastOptions) => Toast; }; +/** + * This gives a better presentation of error data sent from the API (both general platform errors and app-specific errors). + * This uses platform's new Toasts service to prevent modal/toast z-index collision issues. + * This fixes some issues you can see with re-rendering since using a class such as notifications.toasts. + * This also has an adapter and transform for detecting if a bsearch's EsError is present and then adapts that to the + * Kibana error toaster model so that the network error message will be shown rather than a stack trace. + */ 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) => - addError(error, { - ...options, - toastMessage: error.body.message, - }), - [addError] - ); - const _addError = useCallback( (error: unknown, options: ErrorToastOptions) => { - if (isAppError(error)) { - return addAppError(error, options); + if (error != null && isEsError(error)) { + const err = esErrorToRequestError(error); + return addError(err, options); + } else if (isAppError(error)) { + return addError(error, options); + } else if (error instanceof Error) { + return addError(error, options); } else { - if (error instanceof Error) { - return addError(error, options); - } else { - return addError(new Error(String(error)), options); - } + // Best guess that this is a stringable error. + const err = new Error(String(error)); + return addError(err, options); } }, - [addAppError, addError] + [addError] ); return { api: toasts, addError: _addError, addSuccess, addWarning }; }; + +/** + * See this file, we are not allowed to import files such as es_error. + * So instead we say maybe err is on there so that we can unwrap it and get + * our status code from it if possible within the error in our function. + * src/plugins/data/public/search/errors/es_error.tsx + */ +type MaybeESError = IEsError & { err?: Record }; + +/** + * This attempts its best to map between an IEsError which comes from bsearch to a error_toaster + * See the file: src/core/public/notifications/toasts/error_toast.tsx + * + * NOTE: This is brittle at the moment from bsearch and the hope is that better support between + * the error message and formatting of bsearch and the error_toast.tsx from Kibana core will be + * supported in the future. However, for now, this is _hopefully_ temporary. + * + * Also see the file: + * x-pack/plugins/security_solution/public/app/home/setup.tsx + * + * Where this same technique of overriding and changing the stack is occurring. + */ +export const esErrorToRequestError = (error: IEsError & MaybeESError): Error => { + const maybeUnWrapped = error.err != null ? error.err : error; + const statusCode = error.err?.statusCode != null ? `(${error.err.statusCode})` : ''; + const stringifiedError = JSON.stringify(maybeUnWrapped, null, 2); + return { + message: `${error.attributes?.reason ?? error.message} ${statusCode}`, + name: error.attributes?.reason ?? error.message, + stack: stringifiedError, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/utils/api/index.ts b/x-pack/plugins/security_solution/public/common/utils/api/index.ts index 198757e9ceade..513fed36f678c 100644 --- a/x-pack/plugins/security_solution/public/common/utils/api/index.ts +++ b/x-pack/plugins/security_solution/public/common/utils/api/index.ts @@ -7,9 +7,7 @@ import { has } from 'lodash/fp'; -export interface AppError { - name: string; - message: string; +export interface AppError extends Error { body: { message: string; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx index 679aac71c6fdf..004a904828ecf 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx @@ -12,6 +12,8 @@ import { shallow, mount, ReactWrapper } from 'enzyme'; import '../../../../common/mock/match_media'; import { PrePackagedRulesPrompt } from './load_empty_prompt'; import { getPrePackagedRulesStatus } from '../../../containers/detection_engine/rules/api'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -37,6 +39,7 @@ jest.mock('../../../containers/detection_engine/rules/api', () => ({ }), createPrepackagedRules: jest.fn(), })); +jest.mock('../../../../common/hooks/use_app_toasts'); const props = { createPrePackagedRules: jest.fn(), @@ -46,6 +49,14 @@ const props = { }; describe('PrePackagedRulesPrompt', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + it('renders correctly', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx index 91b53c11ddda1..c17d227428391 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx @@ -9,10 +9,21 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { usePrivilegeUser, ReturnPrivilegeUser } from './use_privilege_user'; import * as api from './api'; import { Privilege } from './types'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('usePrivilegeUser', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx index a527123fffb4a..dd4da78db4e8e 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx @@ -6,8 +6,8 @@ */ import { useEffect, useState } from 'react'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; import { getUserPrivilege } from './api'; import * as i18n from './translations'; @@ -44,7 +44,7 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => { hasIndexUpdateDelete: null, hasIndexMaintenance: null, }); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -84,7 +84,7 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => { hasIndexUpdateDelete: false, hasIndexMaintenance: false, }); - errorToToaster({ title: i18n.PRIVILEGE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.PRIVILEGE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -97,7 +97,7 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => { isSubscribed = false; abortCtrl.abort(); }; - }, [dispatchToaster]); + }, [addError]); return { loading, ...privilegeUser }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx index dc6747510a3ea..e8cd501816afe 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx @@ -8,14 +8,22 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useSignalIndex, ReturnSignalIndex } from './use_signal_index'; import * as api from './api'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useSignalIndex', () => { + let appToastsMock: jest.Mocked>; + beforeEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); }); + test('init', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx index 00ecbca338b70..74adc8d36b0aa 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx @@ -7,7 +7,7 @@ import { useEffect, useState } from 'react'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { createSignalIndex, getSignalIndex } from './api'; import * as i18n from './translations'; import { isSecurityAppError } from '../../../../common/utils/api'; @@ -35,7 +35,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { signalIndexMappingOutdated: null, createDeSignalIndex: null, }); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -63,7 +63,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { createDeSignalIndex: createIndex, }); if (isSecurityAppError(error) && error.body.status_code !== 404) { - errorToToaster({ title: i18n.SIGNAL_GET_NAME_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.SIGNAL_GET_NAME_FAILURE }); } } } @@ -93,7 +93,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { signalIndexMappingOutdated: null, createDeSignalIndex: createIndex, }); - errorToToaster({ title: i18n.SIGNAL_POST_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.SIGNAL_POST_FAILURE }); } } } @@ -107,7 +107,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { isSubscribed = false; abortCtrl.abort(); }; - }, [dispatchToaster]); + }, [addError]); return { loading, ...signalIndex }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx index 4532d3427375b..6a527ca00f525 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx @@ -8,12 +8,19 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useRules, UseRules, ReturnRules } from './use_rules'; import * as api from '../api'; +import { useAppToastsMock } from '../../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; jest.mock('../api'); +jest.mock('../../../../../common/hooks/use_app_toasts'); describe('useRules', () => { + let appToastsMock: jest.Mocked>; + beforeEach(() => { jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); }); test('init', async () => { await act(async () => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx index f3c90ae12ae33..b7ef04c79d3da 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx @@ -7,8 +7,8 @@ import { useEffect, useState, useRef } from 'react'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import { FetchRulesResponse, FilterOptions, PaginationOptions, Rule } from '../types'; -import { errorToToaster, useStateToaster } from '../../../../../common/components/toasters'; import { fetchRules } from '../api'; import * as i18n from '../translations'; @@ -34,7 +34,7 @@ export const useRules = ({ const [rules, setRules] = useState(null); const reFetchRules = useRef<() => Promise>(() => Promise.resolve()); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); const filterTags = filterOptions.tags.sort().join(); useEffect(() => { @@ -62,7 +62,7 @@ export const useRules = ({ } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); if (dispatchRulesInReducer != null) { dispatchRulesInReducer([], {}); } diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts index 7fcefe02cfe33..8969843f61a1c 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts @@ -8,7 +8,7 @@ import { Dispatch, useMemo, useReducer, useEffect, useRef } from 'react'; import { EuiBasicTable } from '@elastic/eui'; -import { errorToToaster, useStateToaster } from '../../../../../common/components/toasters'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import * as i18n from '../translations'; import { fetchRules } from '../api'; @@ -65,9 +65,9 @@ export const useRulesTable = (params: UseRulesTableParams): UseRulesTableReturn const reducer = useMemo(() => createRulesTableReducer(tableRef), [tableRef]); const [state, dispatch] = useReducer(reducer, initialState); const facade = useRef(createRulesTableFacade(dispatch)); + const { addError } = useAppToasts(); const reFetchRules = useRef<() => Promise>(() => Promise.resolve()); - const [, dispatchToaster] = useStateToaster(); const { pagination, filterOptions } = state; const filterTags = filterOptions.tags.sort().join(); @@ -95,7 +95,7 @@ export const useRulesTable = (params: UseRulesTableParams): UseRulesTableReturn } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); facade.current.setRules([], {}); } } diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx index 0074808057ca7..d6d6dec6edc6a 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx @@ -10,10 +10,21 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useCreateRule, ReturnCreateRule } from './use_create_rule'; import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getRulesSchemaMock } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useCreateRule', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { const { result } = renderHook(() => useCreateRule()); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx index e9a807d772d8b..d50ef49593f40 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx @@ -7,7 +7,7 @@ import { useEffect, useState, Dispatch } from 'react'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { CreateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; import { createRule } from './api'; @@ -25,7 +25,7 @@ export const useCreateRule = (): ReturnCreateRule => { const [rule, setRule] = useState(null); const [ruleId, setRuleId] = useState(null); const [isLoading, setIsLoading] = useState(false); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -44,7 +44,7 @@ export const useCreateRule = (): ReturnCreateRule => { } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.RULE_ADD_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_ADD_FAILURE }); } } if (isSubscribed) { @@ -58,7 +58,7 @@ export const useCreateRule = (): ReturnCreateRule => { isSubscribed = false; abortCtrl.abort(); }; - }, [rule, dispatchToaster]); + }, [rule, addError]); return [{ isLoading, ruleId }, setRule]; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx index 8107d1ca84fda..9ea8cee106052 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx @@ -12,6 +12,15 @@ import * as api from './api'; import { shallow } from 'enzyme'; import * as i18n from './translations'; +jest.mock('../../../../common/lib/kibana', () => ({ + useKibana: jest.fn(), + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), +})); + jest.mock('./api', () => ({ getPrePackagedRulesStatus: jest.fn(), createPrepackagedRules: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx index 3fbda3e4533ea..be474bbdc4fd8 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -8,11 +8,7 @@ import React, { useCallback, useMemo, useState, useEffect } from 'react'; import { EuiButton } from '@elastic/eui'; -import { - errorToToaster, - useStateToaster, - displaySuccessToast, -} from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { getPrePackagedRulesStatus, createPrepackagedRules } from './api'; import * as i18n from './translations'; @@ -114,7 +110,8 @@ export const usePrePackagedRules = ({ const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); + const { addError, addSuccess } = useAppToasts(); + const getSuccessToastMessage = (result: { rules_installed: number; rules_updated: number; @@ -173,7 +170,7 @@ export const usePrePackagedRules = ({ timelinesNotUpdated: null, }); - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -231,7 +228,7 @@ export const usePrePackagedRules = ({ timelinesNotInstalled: prePackagedRuleStatusResponse.timelines_not_installed, timelinesNotUpdated: prePackagedRuleStatusResponse.timelines_not_updated, }); - displaySuccessToast(getSuccessToastMessage(result), dispatchToaster); + addSuccess(getSuccessToastMessage(result)); stopTimeOut(); resolve(true); } else { @@ -246,10 +243,8 @@ export const usePrePackagedRules = ({ } catch (error) { if (isSubscribed) { setLoadingCreatePrePackagedRules(false); - errorToToaster({ + addError(error, { title: i18n.RULE_AND_TIMELINE_PREPACKAGED_FAILURE, - error, - dispatchToaster, }); resolve(false); } @@ -269,7 +264,8 @@ export const usePrePackagedRules = ({ isAuthenticated, hasEncryptionKey, isSignalIndexExists, - dispatchToaster, + addError, + addSuccess, ]); const prePackagedRuleStatus = useMemo( diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx index 4b062bee6176b..3c87a20dea6bb 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx @@ -8,10 +8,21 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useRule, ReturnRule } from './use_rule'; import * as api from './api'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useRule', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx index 5ecc7904871a4..4e5480a921493 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx @@ -6,8 +6,8 @@ */ import { useEffect, useState } from 'react'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; import { fetchRuleById } from './api'; import { transformInput } from './transforms'; import * as i18n from './translations'; @@ -24,7 +24,7 @@ export type ReturnRule = [boolean, Rule | null]; export const useRule = (id: string | undefined): ReturnRule => { const [rule, setRule] = useState(null); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -45,7 +45,7 @@ export const useRule = (id: string | undefined): ReturnRule => { } catch (error) { if (isSubscribed) { setRule(null); - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -59,7 +59,7 @@ export const useRule = (id: string | undefined): ReturnRule => { isSubscribed = false; abortCtrl.abort(); }; - }, [id, dispatchToaster]); + }, [id, addError]); return [loading, rule]; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx index c773cac5dcfef..96a8b00bf4966 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx @@ -14,8 +14,11 @@ import { } from './use_rule_status'; import * as api from './api'; import { Rule } from './types'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); const testRule: Rule = { actions: [ @@ -67,10 +70,13 @@ const testRule: Rule = { }; describe('useRuleStatus', () => { + let appToastsMock: jest.Mocked>; beforeEach(() => { jest.resetAllMocks(); jest.restoreAllMocks(); jest.clearAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); }); afterEach(async () => { cleanup(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx index 1f221c9abc798..e3e2351b40a32 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx @@ -6,8 +6,8 @@ */ import { useEffect, useRef, useState } from 'react'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; import { RuleStatusRowItemType } from '../../../pages/detection_engine/rules/all/columns'; import { getRuleStatusById, getRulesStatusByIds } from './api'; import * as i18n from './translations'; @@ -30,7 +30,7 @@ export const useRuleStatus = (id: string | undefined | null): ReturnRuleStatus = const [ruleStatus, setRuleStatus] = useState(null); const fetchRuleStatus = useRef(null); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -50,7 +50,7 @@ export const useRuleStatus = (id: string | undefined | null): ReturnRuleStatus = } catch (error) { if (isSubscribed) { setRuleStatus(null); - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -65,7 +65,7 @@ export const useRuleStatus = (id: string | undefined | null): ReturnRuleStatus = isSubscribed = false; abortCtrl.abort(); }; - }, [id, dispatchToaster]); + }, [id, addError]); return [loading, ruleStatus, fetchRuleStatus.current]; }; @@ -79,7 +79,7 @@ export const useRuleStatus = (id: string | undefined | null): ReturnRuleStatus = export const useRulesStatuses = (rules: Rules): ReturnRulesStatuses => { const [rulesStatuses, setRuleStatuses] = useState([]); const [loading, setLoading] = useState(false); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -106,7 +106,7 @@ export const useRulesStatuses = (rules: Rules): ReturnRulesStatuses => { } catch (error) { if (isSubscribed) { setRuleStatuses([]); - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -122,7 +122,7 @@ export const useRulesStatuses = (rules: Rules): ReturnRulesStatuses => { isSubscribed = false; abortCtrl.abort(); }; - }, [rules, dispatchToaster]); + }, [rules, addError]); return { loading, rulesStatuses }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx index f9488caaa9132..e177d36057b1d 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx @@ -6,11 +6,22 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; import { useTags, ReturnTags } from './use_tags'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useTags', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => useTags()); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx index 5681b076aa6bb..5f16cb593a516 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx @@ -7,7 +7,7 @@ import { noop } from 'lodash/fp'; import { useEffect, useState, useRef } from 'react'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { fetchTags } from './api'; import * as i18n from './translations'; @@ -20,8 +20,8 @@ export type ReturnTags = [boolean, string[], () => void]; export const useTags = (): ReturnTags => { const [tags, setTags] = useState([]); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); const reFetchTags = useRef<() => void>(noop); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -39,7 +39,7 @@ export const useTags = (): ReturnTags => { } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.TAG_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.TAG_FETCH_FAILURE }); } } if (isSubscribed) { @@ -54,7 +54,7 @@ export const useTags = (): ReturnTags => { isSubscribed = false; abortCtrl.abort(); }; - }, [dispatchToaster]); + }, [addError]); return [loading, tags, reFetchTags.current]; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx index c000870e8e51f..3b16d0266e566 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx @@ -9,10 +9,21 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useUpdateRule, ReturnUpdateRule } from './use_update_rule'; import { getUpdateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useUpdateRule', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { const { result } = renderHook(() => useUpdateRule()); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx index 046702323db38..a5953b6ec3e65 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx @@ -7,7 +7,7 @@ import { useEffect, useState, Dispatch } from 'react'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { UpdateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; import { transformOutput } from './transforms'; @@ -26,7 +26,7 @@ export const useUpdateRule = (): ReturnUpdateRule => { const [rule, setRule] = useState(null); const [isSaved, setIsSaved] = useState(false); const [isLoading, setIsLoading] = useState(false); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -42,7 +42,7 @@ export const useUpdateRule = (): ReturnUpdateRule => { } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.RULE_ADD_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_ADD_FAILURE }); } } if (isSubscribed) { @@ -56,7 +56,7 @@ export const useUpdateRule = (): ReturnUpdateRule => { isSubscribed = false; abortCtrl.abort(); }; - }, [rule, dispatchToaster]); + }, [rule, addError]); return [{ isLoading, isSaved }, setRule]; }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx index 5cfa5ecd225ec..146b7e8470718 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx @@ -15,6 +15,7 @@ import { } from '@elastic/eui'; import { History } from 'history'; +import { useAppToasts } from '../../../../../../common/hooks/use_app_toasts'; import { AutoDownload } from '../../../../../../common/components/auto_download/auto_download'; import { NamespaceType } from '../../../../../../../../lists/common'; import { useKibana } from '../../../../../../common/lib/kibana'; @@ -88,6 +89,7 @@ export const ExceptionListsTable = React.memo( const [deletingListIds, setDeletingListIds] = useState([]); const [exportingListIds, setExportingListIds] = useState([]); const [exportDownload, setExportDownload] = useState<{ name?: string; blob?: Blob }>({}); + const { addError } = useAppToasts(); const handleDeleteSuccess = useCallback( (listId?: string) => () => { @@ -100,12 +102,11 @@ export const ExceptionListsTable = React.memo( const handleDeleteError = useCallback( (err: Error & { body?: { message: string } }): void => { - notifications.toasts.addError(err, { + addError(err, { title: i18n.EXCEPTION_DELETE_ERROR, - toastMessage: err.body != null ? err.body.message : err.message, }); }, - [notifications.toasts] + [addError] ); const handleDelete = useCallback( @@ -170,9 +171,9 @@ export const ExceptionListsTable = React.memo( const handleExportError = useCallback( (err: Error) => { - notifications.toasts.addError(err, { title: i18n.EXCEPTION_EXPORT_ERROR }); + addError(err, { title: i18n.EXCEPTION_EXPORT_ERROR }); }, - [notifications.toasts] + [addError] ); const handleExport = useCallback( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx index 3ea6000401872..a84a60af51b39 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx @@ -10,8 +10,19 @@ import { mount } from 'enzyme'; import { act } from '@testing-library/react'; import { RulesTableFilters } from './rules_table_filters'; +import { useAppToastsMock } from '../../../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../../../common/hooks/use_app_toasts'; +jest.mock('../../../../../../common/hooks/use_app_toasts'); describe('RulesTableFilters', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + it('renders no numbers next to rule type button filter if none exist', async () => { await act(async () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx index 5b443b73f11e2..9622610f3c637 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx @@ -12,6 +12,8 @@ import '../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../common/mock'; import { CreateRulePage } from './index'; import { useUserData } from '../../../../components/user_info'; +import { useAppToastsMock } from '../../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -27,8 +29,16 @@ jest.mock('react-router-dom', () => { jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../components/user_info'); +jest.mock('../../../../../common/hooks/use_app_toasts'); describe('CreateRulePage', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + it('renders correctly', () => { (useUserData as jest.Mock).mockReturnValue([{}]); const wrapper = shallow(, { wrappingComponent: TestProviders }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx index 5f485dcaa0195..e7cdfbe268fe6 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx @@ -13,6 +13,8 @@ import { TestProviders } from '../../../../../common/mock'; import { EditRulePage } from './index'; import { useUserData } from '../../../../components/user_info'; import { useParams } from 'react-router-dom'; +import { useAppToastsMock } from '../../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../../common/components/link_to'); @@ -26,8 +28,16 @@ jest.mock('react-router-dom', () => { useParams: jest.fn(), }; }); +jest.mock('../../../../../common/hooks/use_app_toasts'); describe('EditRulePage', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + it('renders correctly', () => { (useUserData as jest.Mock).mockReturnValue([{}]); (useParams as jest.Mock).mockReturnValue({}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx index 0ffeaa4224544..bcd5ccdc0b5ac 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx @@ -14,6 +14,8 @@ import { useUserData } from '../../../components/user_info'; import { waitFor } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; import { getPrePackagedRulesStatus } from '../../../containers/detection_engine/rules/api'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -73,10 +75,15 @@ jest.mock('../../../components/rules/pre_packaged_rules/update_callout', () => { UpdatePrePackagedRulesCallOut: jest.fn().mockReturnValue(
), }; }); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('RulesPage', () => { + let appToastsMock: jest.Mocked>; + beforeAll(() => { (useUserData as jest.Mock).mockReturnValue([{}]); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); }); it('renders AllRules', () => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx index f1efdd2e3c432..c31094b5778d5 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx @@ -33,6 +33,7 @@ import { InspectResponse } from '../../../types'; import { hostsModel, hostsSelectors } from '../../store'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'hostsAuthenticationsQuery'; @@ -71,7 +72,7 @@ export const useAuthentications = ({ const { activePage, limit } = useDeepEqualSelector((state) => pick(['activePage', 'limit'], getAuthenticationsSelector(state, type)) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -80,6 +81,7 @@ export const useAuthentications = ({ authenticationsRequest, setAuthenticationsRequest, ] = useState(null); + const { addError, addWarning } = useAppToasts(); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -145,15 +147,14 @@ export const useAuthentications = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_AUTHENTICATIONS); + addWarning(i18n.ERROR_AUTHENTICATIONS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_AUTHENTICATIONS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -164,7 +165,7 @@ export const useAuthentications = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx index 1eaa89575de26..dd55bdb4c6948 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { useKibana } from '../../../../common/lib/kibana'; import { @@ -55,7 +56,7 @@ export const useHostDetails = ({ skip = false, startDate, }: UseHostDetails): [boolean, HostDetailsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -63,6 +64,7 @@ export const useHostDetails = ({ const [hostDetailsRequest, setHostDetailsRequest] = useState( null ); + const { addError, addWarning } = useAppToasts(); const [hostDetailsResponse, setHostDetailsResponse] = useState({ endDate, @@ -104,16 +106,14 @@ export const useHostDetails = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOST_OVERVIEW); + addWarning(i18n.ERROR_HOST_OVERVIEW); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOST_OVERVIEW, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -124,7 +124,7 @@ export const useHostDetails = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx index 380e6b05471a8..a3703ab64beda 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -9,6 +9,7 @@ import deepEqual from 'fast-deep-equal'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useKibana } from '../../../../common/lib/kibana'; import { HostsQueries, @@ -45,7 +46,7 @@ export const useFirstLastSeenHost = ({ indexNames, order, }: UseHostFirstLastSeen): [boolean, FirstLastSeenHostArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); @@ -69,6 +70,7 @@ export const useFirstLastSeenHost = ({ id: ID, } ); + const { addError, addWarning } = useAppToasts(); const firstLastSeenHostSearch = useCallback( (request: HostFirstLastSeenRequestOptions) => { @@ -93,8 +95,7 @@ export const useFirstLastSeenHost = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_FIRST_LAST_SEEN_HOST); + addWarning(i18n.ERROR_FIRST_LAST_SEEN_HOST); searchSubscription$.current.unsubscribe(); } }, @@ -104,9 +105,8 @@ export const useFirstLastSeenHost = ({ ...prevResponse, errorMessage: msg, })); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_FIRST_LAST_SEEN_HOST, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -116,7 +116,7 @@ export const useFirstLastSeenHost = ({ abortCtrl.current.abort(); asyncSearch(); }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx index 383c4c233914f..7bf681092c075 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx @@ -30,6 +30,7 @@ import * as i18n from './translations'; import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'hostsAllQuery'; @@ -70,12 +71,13 @@ export const useAllHost = ({ const { activePage, direction, limit, sortField } = useDeepEqualSelector((state: State) => getHostsSelector(state, type) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [hostsRequest, setHostRequest] = useState(null); + const { addError, addWarning } = useAppToasts(); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -143,14 +145,13 @@ export const useAllHost = ({ searchSubscription.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_ALL_HOST); + addWarning(i18n.ERROR_ALL_HOST); searchSubscription.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ title: i18n.FAIL_ALL_HOST, text: msg.message }); + addError(msg, { title: i18n.FAIL_ALL_HOST }); searchSubscription.current.unsubscribe(); }, }); @@ -160,7 +161,7 @@ export const useAllHost = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx index ad3c7e0e829fb..6a3323da4fb44 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -49,7 +50,7 @@ export const useHostsKpiAuthentications = ({ skip = false, startDate, }: UseHostsKpiAuthentications): [boolean, HostsKpiAuthenticationsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -75,6 +76,7 @@ export const useHostsKpiAuthentications = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const hostsKpiAuthenticationsSearch = useCallback( (request: HostsKpiAuthenticationsRequestOptions | null) => { @@ -110,16 +112,14 @@ export const useHostsKpiAuthentications = ({ searchSubscription$.current.unsubscribe(); } else if (response.isPartial && !response.isRunning) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_AUTHENTICATIONS); + addWarning(i18n.ERROR_HOSTS_KPI_AUTHENTICATIONS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOSTS_KPI_AUTHENTICATIONS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -130,7 +130,7 @@ export const useHostsKpiAuthentications = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx index 8ed1aaecb6f0e..5af91539e8be3 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -48,7 +49,7 @@ export const useHostsKpiHosts = ({ skip = false, startDate, }: UseHostsKpiHosts): [boolean, HostsKpiHostsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -69,6 +70,7 @@ export const useHostsKpiHosts = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const hostsKpiHostsSearch = useCallback( (request: HostsKpiHostsRequestOptions | null) => { @@ -98,16 +100,14 @@ export const useHostsKpiHosts = ({ searchSubscription$.current.unsubscribe(); } else if (response.isPartial && !response.isRunning) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_HOSTS); + addWarning(i18n.ERROR_HOSTS_KPI_HOSTS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOSTS_KPI_HOSTS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -118,7 +118,7 @@ export const useHostsKpiHosts = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx index b34de267f4519..9a72fa1d6cfca 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -49,7 +50,7 @@ export const useHostsKpiUniqueIps = ({ skip = false, startDate, }: UseHostsKpiUniqueIps): [boolean, HostsKpiUniqueIpsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -74,6 +75,7 @@ export const useHostsKpiUniqueIps = ({ refetch: refetch.current, } ); + const { addError, addWarning } = useAppToasts(); const hostsKpiUniqueIpsSearch = useCallback( (request: HostsKpiUniqueIpsRequestOptions | null) => { @@ -105,16 +107,14 @@ export const useHostsKpiUniqueIps = ({ searchSubscription$.current.unsubscribe(); } else if (response.isPartial && !response.isRunning) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_UNIQUE_IPS); + addWarning(i18n.ERROR_HOSTS_KPI_UNIQUE_IPS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOSTS_KPI_UNIQUE_IPS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -125,7 +125,7 @@ export const useHostsKpiUniqueIps = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx index 1e07b94b55b74..e94873dee5632 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx @@ -32,6 +32,7 @@ import { ESTermQuery } from '../../../../common/typed_json'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'hostsUncommonProcessesQuery'; @@ -72,7 +73,7 @@ export const useUncommonProcesses = ({ const { activePage, limit } = useDeepEqualSelector((state: State) => getUncommonProcessesSelector(state, type) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -81,6 +82,7 @@ export const useUncommonProcesses = ({ uncommonProcessesRequest, setUncommonProcessesRequest, ] = useState(null); + const { addError, addWarning } = useAppToasts(); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -150,15 +152,14 @@ export const useUncommonProcesses = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_UNCOMMON_PROCESSES); + addWarning(i18n.ERROR_UNCOMMON_PROCESSES); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_UNCOMMON_PROCESSES, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -169,7 +170,7 @@ export const useUncommonProcesses = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx index 6bbe7f8f43773..cf7d8e05858d5 100644 --- a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx @@ -24,6 +24,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkDetailsQuery'; @@ -52,7 +53,7 @@ export const useNetworkDetails = ({ skip, ip, }: UseNetworkDetails): [boolean, NetworkDetailsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -73,6 +74,7 @@ export const useNetworkDetails = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkDetailsSearch = useCallback( (request: NetworkDetailsRequestOptions | null) => { @@ -100,16 +102,14 @@ export const useNetworkDetails = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_DETAILS); + addWarning(i18n.ERROR_NETWORK_DETAILS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_DETAILS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -120,7 +120,7 @@ export const useNetworkDetails = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx index 345aee4de2df2..c835aa6c6a3e3 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -53,7 +54,7 @@ export const useNetworkKpiDns = ({ skip = false, startDate, }: UseNetworkKpiDns): [boolean, NetworkKpiDnsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -73,6 +74,7 @@ export const useNetworkKpiDns = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiDnsSearch = useCallback( (request: NetworkKpiDnsRequestOptions | null) => { @@ -102,16 +104,14 @@ export const useNetworkKpiDns = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_DNS); + addWarning(i18n.ERROR_NETWORK_KPI_DNS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_DNS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -122,7 +122,7 @@ export const useNetworkKpiDns = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx index 6dd3df1055f9c..2e4f3b83e6708 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -53,7 +54,7 @@ export const useNetworkKpiNetworkEvents = ({ skip = false, startDate, }: UseNetworkKpiNetworkEvents): [boolean, NetworkKpiNetworkEventsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -76,6 +77,7 @@ export const useNetworkKpiNetworkEvents = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiNetworkEventsSearch = useCallback( (request: NetworkKpiNetworkEventsRequestOptions | null) => { @@ -108,16 +110,14 @@ export const useNetworkKpiNetworkEvents = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_NETWORK_EVENTS); + addWarning(i18n.ERROR_NETWORK_KPI_NETWORK_EVENTS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_NETWORK_EVENTS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -128,7 +128,7 @@ export const useNetworkKpiNetworkEvents = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx index dfc7d0a28db79..b9d3e8639c560 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -53,7 +54,7 @@ export const useNetworkKpiTlsHandshakes = ({ skip = false, startDate, }: UseNetworkKpiTlsHandshakes): [boolean, NetworkKpiTlsHandshakesArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -76,6 +77,7 @@ export const useNetworkKpiTlsHandshakes = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiTlsHandshakesSearch = useCallback( (request: NetworkKpiTlsHandshakesRequestOptions | null) => { @@ -107,16 +109,14 @@ export const useNetworkKpiTlsHandshakes = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_TLS_HANDSHAKES); + addWarning(i18n.ERROR_NETWORK_KPI_TLS_HANDSHAKES); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_TLS_HANDSHAKES, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -127,7 +127,7 @@ export const useNetworkKpiTlsHandshakes = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx index 08c4d917f5da3..2699d63144be1 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -53,7 +54,7 @@ export const useNetworkKpiUniqueFlows = ({ skip = false, startDate, }: UseNetworkKpiUniqueFlows): [boolean, NetworkKpiUniqueFlowsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -76,6 +77,7 @@ export const useNetworkKpiUniqueFlows = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiUniqueFlowsSearch = useCallback( (request: NetworkKpiUniqueFlowsRequestOptions | null) => { @@ -108,16 +110,14 @@ export const useNetworkKpiUniqueFlows = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_FLOWS); + addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_FLOWS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_UNIQUE_FLOWS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -128,7 +128,7 @@ export const useNetworkKpiUniqueFlows = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx index a532f4f11a301..488c526134525 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -57,7 +58,7 @@ export const useNetworkKpiUniquePrivateIps = ({ skip = false, startDate, }: UseNetworkKpiUniquePrivateIps): [boolean, NetworkKpiUniquePrivateIpsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -83,6 +84,7 @@ export const useNetworkKpiUniquePrivateIps = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiUniquePrivateIpsSearch = useCallback( (request: NetworkKpiUniquePrivateIpsRequestOptions | null) => { @@ -119,16 +121,14 @@ export const useNetworkKpiUniquePrivateIps = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_PRIVATE_IPS); + addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_PRIVATE_IPS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_UNIQUE_PRIVATE_IPS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -139,7 +139,7 @@ export const useNetworkKpiUniquePrivateIps = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx index 5ce31bada520b..47e60f27a7dbd 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx @@ -30,6 +30,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkDnsQuery'; @@ -68,7 +69,7 @@ export const useNetworkDns = ({ }: UseNetworkDns): [boolean, NetworkDnsArgs] => { const getNetworkDnsSelector = useMemo(() => networkSelectors.dnsSelector(), []); const { activePage, sort, isPtrIncluded, limit } = useDeepEqualSelector(getNetworkDnsSelector); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -110,6 +111,7 @@ export const useNetworkDns = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkDnsSearch = useCallback( (request: NetworkDnsRequestOptions | null) => { @@ -142,16 +144,14 @@ export const useNetworkDns = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_DNS); + addWarning(i18n.ERROR_NETWORK_DNS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_DNS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -162,7 +162,7 @@ export const useNetworkDns = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx index d1ff9da1fa6c2..98105f5cac25a 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx @@ -29,6 +29,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import * as i18n from './translations'; import { InspectResponse } from '../../../types'; import { getInspectResponse } from '../../../helpers'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkHttpQuery'; @@ -67,7 +68,7 @@ export const useNetworkHttp = ({ }: UseNetworkHttp): [boolean, NetworkHttpArgs] => { const getHttpSelector = useMemo(() => networkSelectors.httpSelector(), []); const { activePage, limit, sort } = useDeepEqualSelector((state) => getHttpSelector(state, type)); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -108,6 +109,7 @@ export const useNetworkHttp = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkHttpSearch = useCallback( (request: NetworkHttpRequestOptions | null) => { @@ -139,16 +141,14 @@ export const useNetworkHttp = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_HTTP); + addWarning(i18n.ERROR_NETWORK_HTTP); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_HTTP, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -159,7 +159,7 @@ export const useNetworkHttp = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx index 405957d98055e..e7f3cf3f2675a 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx @@ -29,6 +29,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkTopCountriesQuery'; @@ -68,7 +69,7 @@ export const useNetworkTopCountries = ({ const { activePage, limit, sort } = useDeepEqualSelector((state) => getTopCountriesSelector(state, type, flowTarget) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -95,6 +96,7 @@ export const useNetworkTopCountries = ({ }, [limit] ); + const { addError, addWarning } = useAppToasts(); const [ networkTopCountriesResponse, @@ -147,16 +149,14 @@ export const useNetworkTopCountries = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_TOP_COUNTRIES); + addWarning(i18n.ERROR_NETWORK_TOP_COUNTRIES); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_TOP_COUNTRIES, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -167,7 +167,7 @@ export const useNetworkTopCountries = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addWarning, addError, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx index 9c6a4b3d1147f..3cbaf0fbc976c 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx @@ -29,6 +29,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkTopNFlowQuery'; @@ -68,7 +69,7 @@ export const useNetworkTopNFlow = ({ const { activePage, limit, sort } = useDeepEqualSelector((state) => getTopNFlowSelector(state, type, flowTarget) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -112,6 +113,7 @@ export const useNetworkTopNFlow = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkTopNFlowSearch = useCallback( (request: NetworkTopNFlowRequestOptions | null) => { @@ -143,16 +145,14 @@ export const useNetworkTopNFlow = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_TOP_N_FLOW); + addWarning(i18n.ERROR_NETWORK_TOP_N_FLOW); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_TOP_N_FLOW, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -163,7 +163,7 @@ export const useNetworkTopNFlow = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx index 49a7064113c30..754f0cac8868c 100644 --- a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx @@ -27,6 +27,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { FlowTargetSourceDest, PageInfoPaginated } from '../../../../common/search_strategy'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkTlsQuery'; @@ -68,7 +69,7 @@ export const useNetworkTls = ({ const { activePage, limit, sort } = useDeepEqualSelector((state) => getTlsSelector(state, type, flowTarget) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -109,6 +110,7 @@ export const useNetworkTls = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkTlsSearch = useCallback( (request: NetworkTlsRequestOptions | null) => { @@ -141,16 +143,14 @@ export const useNetworkTls = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_TLS); + addWarning(i18n.ERROR_NETWORK_TLS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_TLS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -161,7 +161,7 @@ export const useNetworkTls = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/users/index.tsx b/x-pack/plugins/security_solution/public/network/containers/users/index.tsx index e000981733eed..d4be09f97591d 100644 --- a/x-pack/plugins/security_solution/public/network/containers/users/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/users/index.tsx @@ -29,6 +29,7 @@ import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import { PageInfoPaginated } from '../../../../common/search_strategy'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkUsersQuery'; @@ -65,7 +66,7 @@ export const useNetworkUsers = ({ }: UseNetworkUsers): [boolean, NetworkUsersArgs] => { const getNetworkUsersSelector = useMemo(() => networkSelectors.usersSelector(), []); const { activePage, sort, limit } = useDeepEqualSelector(getNetworkUsersSelector); - const { data, notifications, uiSettings } = useKibana().services; + const { data, uiSettings } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -109,6 +110,7 @@ export const useNetworkUsers = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkUsersSearch = useCallback( (request: NetworkUsersRequestOptions | null) => { @@ -140,16 +142,14 @@ export const useNetworkUsers = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_USERS); + addWarning(i18n.ERROR_NETWORK_USERS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_USERS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -160,7 +160,7 @@ export const useNetworkUsers = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx index 8b17a7288eae3..52b58439af0ab 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx @@ -23,6 +23,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; export const ID = 'overviewHostQuery'; @@ -49,7 +50,7 @@ export const useHostOverview = ({ skip = false, startDate, }: UseHostOverview): [boolean, HostOverviewArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -66,6 +67,7 @@ export const useHostOverview = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const overviewHostSearch = useCallback( (request: HostOverviewRequestOptions | null) => { @@ -95,16 +97,14 @@ export const useHostOverview = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOST_OVERVIEW); + addWarning(i18n.ERROR_HOST_OVERVIEW); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOST_OVERVIEW, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -115,7 +115,7 @@ export const useHostOverview = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx index cf0774a02db3b..846c40994aac2 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx @@ -23,6 +23,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; export const ID = 'overviewNetworkQuery'; @@ -49,7 +50,7 @@ export const useNetworkOverview = ({ skip = false, startDate, }: UseNetworkOverview): [boolean, NetworkOverviewArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -69,6 +70,7 @@ export const useNetworkOverview = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const overviewNetworkSearch = useCallback( (request: NetworkOverviewRequestOptions | null) => { @@ -98,16 +100,14 @@ export const useNetworkOverview = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_OVERVIEW); + addWarning(i18n.ERROR_NETWORK_OVERVIEW); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_OVERVIEW, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -118,7 +118,7 @@ export const useNetworkOverview = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx index 7e4924eacda4b..37fdd5a444b2b 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx @@ -20,6 +20,9 @@ import { TimelineEventsDetailsStrategyResponse, } from '../../../../common/search_strategy'; import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/public'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import * as i18n from './translations'; + export interface EventsArgs { detailsData: TimelineEventsDetailsItem[] | null; } @@ -37,7 +40,7 @@ export const useTimelineEventsDetails = ({ eventId, skip, }: UseTimelineEventsDetailsProps): [boolean, EventsArgs['detailsData']] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -46,6 +49,7 @@ export const useTimelineEventsDetails = ({ timelineDetailsRequest, setTimelineDetailsRequest, ] = useState(null); + const { addError, addWarning } = useAppToasts(); const [timelineDetailsResponse, setTimelineDetailsResponse] = useState( null @@ -77,14 +81,13 @@ export const useTimelineEventsDetails = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning('An error has occurred'); + addWarning(i18n.FAIL_TIMELINE_DETAILS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ title: 'Failed to run search', text: msg.message }); + addError(msg, { title: i18n.FAIL_TIMELINE_SEARCH_DETAILS }); searchSubscription$.current.unsubscribe(); }, }); @@ -94,7 +97,7 @@ export const useTimelineEventsDetails = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/translations.ts b/x-pack/plugins/security_solution/public/timelines/containers/details/translations.ts new file mode 100644 index 0000000000000..d11984b967db9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/containers/details/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const FAIL_TIMELINE_DETAILS = i18n.translate( + 'xpack.securitySolution.timeline.failDescription', + { + defaultMessage: 'An error has occurred', + } +); + +export const FAIL_TIMELINE_SEARCH_DETAILS = i18n.translate( + 'xpack.securitySolution.timeline.failSearchDescription', + { + defaultMessage: 'Failed to run search', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx index 496107e910d76..1032d0ec1672a 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx @@ -27,6 +27,11 @@ const mockEvents = mockTimelineData.filter((i, index) => index <= 11); const mockSearch = jest.fn(); jest.mock('../../common/lib/kibana', () => ({ + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), useKibana: jest.fn().mockReturnValue({ services: { application: { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 83b511f95bc2a..92199336b978c 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -40,6 +40,7 @@ import { TimelineEqlRequestOptions, TimelineEqlResponse, } from '../../../common/search_strategy/timeline/events/eql'; +import { useAppToasts } from '../../common/hooks/use_app_toasts'; export interface TimelineArgs { events: TimelineItem[]; @@ -138,7 +139,7 @@ export const useTimelineEvents = ({ }: UseTimelineEventsProps): [boolean, TimelineArgs] => { const [{ pageName }] = useRouteSpy(); const dispatch = useDispatch(); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -194,6 +195,7 @@ export const useTimelineEvents = ({ loadPage: wrappedLoadPage, updatedAt: 0, }); + const { addError, addWarning } = useAppToasts(); const timelineSearch = useCallback( (request: TimelineRequest | null) => { @@ -242,15 +244,14 @@ export const useTimelineEvents = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_TIMELINE_EVENTS); + addWarning(i18n.ERROR_TIMELINE_EVENTS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_TIMELINE_EVENTS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -300,7 +301,7 @@ export const useTimelineEvents = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, id, notifications.toasts, pageName, refetchGrid, skip, wrappedLoadPage] + [data.search, id, addWarning, addError, pageName, refetchGrid, skip, wrappedLoadPage] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx index cf5f44a65ab96..4a6eab13ba4f1 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx @@ -21,6 +21,8 @@ import { } from '../../../../common/search_strategy'; import { ESQuery } from '../../../../common/typed_json'; import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/public'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import * as i18n from './translations'; export interface UseTimelineKpiProps { timerange: TimerangeInput; @@ -37,7 +39,7 @@ export const useTimelineKpis = ({ defaultIndex, isBlankTimeline, }: UseTimelineKpiProps): [boolean, TimelineKpiStrategyResponse | null] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -49,6 +51,8 @@ export const useTimelineKpis = ({ timelineKpiResponse, setTimelineKpiResponse, ] = useState(null); + const { addError, addWarning } = useAppToasts(); + const timelineKpiSearch = useCallback( (request: TimelineRequestBasicOptions | null) => { if (request == null) { @@ -71,13 +75,13 @@ export const useTimelineKpis = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning('An error has occurred'); + addWarning(i18n.FAIL_TIMELINE_KPI_DETAILS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger('Failed to load KPIs'); + addError(msg, { title: i18n.FAIL_TIMELINE_KPI_SEARCH_DETAILS }); searchSubscription$.current.unsubscribe(); }, }); @@ -87,7 +91,7 @@ export const useTimelineKpis = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/kpis/translations.ts b/x-pack/plugins/security_solution/public/timelines/containers/kpis/translations.ts new file mode 100644 index 0000000000000..1a487ef8127f2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/containers/kpis/translations.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const FAIL_TIMELINE_KPI_DETAILS = i18n.translate( + 'xpack.securitySolution.timeline.kpiFailDescription', + { + defaultMessage: 'An error has occurred', + } +); + +export const FAIL_TIMELINE_KPI_SEARCH_DETAILS = i18n.translate( + 'xpack.securitySolution.timeline.kpiFailSearchDescription', + { + defaultMessage: 'Failed to load KPIs', + } +);