diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index d5eeef0f1e768..79383676266f5 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -29,8 +29,8 @@ import { } from '../../../../../public/lists_plugin_deps'; import * as i18n from './translations'; import { TimelineNonEcsData, Ecs } from '../../../../graphql/types'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; import { useKibana } from '../../../lib/kibana'; -import { errorToToaster, displaySuccessToast, useStateToaster } from '../../toasters'; import { ExceptionBuilder } from '../builder'; import { Loader } from '../../loader'; import { useAddOrUpdateException } from '../use_add_exception'; @@ -115,7 +115,7 @@ export const AddExceptionModal = memo(function AddExceptionModal({ Array >([]); const [fetchOrCreateListError, setFetchOrCreateListError] = useState(false); - const [, dispatchToaster] = useStateToaster(); + const { addError, addSuccess } = useAppToasts(); const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns( @@ -124,15 +124,15 @@ export const AddExceptionModal = memo(function AddExceptionModal({ const onError = useCallback( (error: Error) => { - errorToToaster({ title: i18n.ADD_EXCEPTION_ERROR, error, dispatchToaster }); + addError(error, { title: i18n.ADD_EXCEPTION_ERROR }); onCancel(); }, - [dispatchToaster, onCancel] + [addError, onCancel] ); const onSuccess = useCallback(() => { - displaySuccessToast(i18n.ADD_EXCEPTION_SUCCESS, dispatchToaster); + addSuccess(i18n.ADD_EXCEPTION_SUCCESS); onConfirm(shouldCloseAlert); - }, [dispatchToaster, onConfirm, shouldCloseAlert]); + }, [addSuccess, onConfirm, shouldCloseAlert]); const [{ isLoading: addExceptionIsLoading }, addOrUpdateExceptionItems] = useAddOrUpdateException( { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx index 73933d483e2cb..dbc70dfe21dd0 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx @@ -30,7 +30,7 @@ import { } from '../../../../../public/lists_plugin_deps'; import * as i18n from './translations'; import { useKibana } from '../../../lib/kibana'; -import { errorToToaster, displaySuccessToast, useStateToaster } from '../../toasters'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; import { ExceptionBuilder } from '../builder'; import { useAddOrUpdateException } from '../use_add_exception'; import { AddExceptionComments } from '../add_exception_comments'; @@ -93,7 +93,7 @@ export const EditExceptionModal = memo(function EditExceptionModal({ const [exceptionItemsToAdd, setExceptionItemsToAdd] = useState< Array >([]); - const [, dispatchToaster] = useStateToaster(); + const { addError, addSuccess } = useAppToasts(); const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns( @@ -102,15 +102,15 @@ export const EditExceptionModal = memo(function EditExceptionModal({ const onError = useCallback( (error) => { - errorToToaster({ title: i18n.EDIT_EXCEPTION_ERROR, error, dispatchToaster }); + addError(error, { title: i18n.EDIT_EXCEPTION_ERROR }); onCancel(); }, - [dispatchToaster, onCancel] + [addError, onCancel] ); const onSuccess = useCallback(() => { - displaySuccessToast(i18n.EDIT_EXCEPTION_SUCCESS, dispatchToaster); + addSuccess(i18n.EDIT_EXCEPTION_SUCCESS); onConfirm(); - }, [dispatchToaster, onConfirm]); + }, [addSuccess, onConfirm]); const [{ isLoading: addExceptionIsLoading }, addOrUpdateExceptionItems] = useAddOrUpdateException( { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx index 0e2908fc34232..90752f9450e4c 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exception_item/index.test.tsx @@ -13,6 +13,8 @@ import { ExceptionItem } from './'; import { getExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { getCommentsArrayMock } from '../../../../../../../lists/common/schemas/types/comments.mock'; +jest.mock('../../../../lib/kibana'); + describe('ExceptionItem', () => { it('it renders ExceptionDetails and ExceptionEntries', () => { const exceptionItem = getExceptionListItemSchemaMock(); 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 e7cc389d4c06b..47c5588a12830 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 @@ -9,7 +9,7 @@ import { isError } from 'lodash/fp'; import { AppToast, ActionToaster } from './'; import { isToasterError } from './errors'; -import { isApiError } from '../../utils/api'; +import { isAppError } from '../../utils/api'; /** * Displays an error toast for the provided title and message @@ -114,7 +114,7 @@ export const errorToToaster = ({ iconType, errors: error.messages, }; - } else if (isApiError(error)) { + } else if (isAppError(error)) { toast = { id, title, 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 new file mode 100644 index 0000000000000..e0e629793952a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { renderHook } from '@testing-library/react-hooks'; + +import { useToasts } from '../lib/kibana'; +import { useAppToasts } from './use_app_toasts'; + +jest.mock('../lib/kibana'); + +describe('useDeleteList', () => { + let addErrorMock: jest.Mock; + let addSuccessMock: jest.Mock; + + beforeEach(() => { + addErrorMock = jest.fn(); + addSuccessMock = jest.fn(); + (useToasts as jest.Mock).mockImplementation(() => ({ + addError: addErrorMock, + addSuccess: addSuccessMock, + })); + }); + + it('works normally with a regular error', async () => { + const error = new Error('regular error'); + const { result } = renderHook(() => useAppToasts()); + + result.current.addError(error, { title: 'title' }); + + 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' }, + }; + + const { result } = renderHook(() => useAppToasts()); + + result.current.addError(kibanaApiError, { title: 'title' }); + + expect(addErrorMock).toHaveBeenCalledWith(kibanaApiError, { + title: 'title', + toastMessage: 'Detailed Message', + }); + }); + + it('converts an unknown error to an Error', () => { + const unknownError = undefined; + + const { result } = renderHook(() => useAppToasts()); + + result.current.addError(unknownError, { title: 'title' }); + + expect(addErrorMock).toHaveBeenCalledWith(Error(`${undefined}`), { + title: 'title', + }); + }); +}); 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 new file mode 100644 index 0000000000000..bc59d87100058 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback, useRef } from 'react'; + +import { ErrorToastOptions, ToastsStart, Toast } from '../../../../../../src/core/public'; +import { useToasts } from '../lib/kibana'; +import { isAppError, AppError } from '../utils/api'; + +export type UseAppToasts = Pick & { + api: ToastsStart; + addError: (error: unknown, options: ErrorToastOptions) => Toast; +}; + +export const useAppToasts = (): UseAppToasts => { + const toasts = useToasts(); + const addError = useRef(toasts.addError.bind(toasts)).current; + const addSuccess = useRef(toasts.addSuccess.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); + } else { + if (error instanceof Error) { + return addError(error, options); + } else { + return addError(new Error(String(error)), options); + } + } + }, + [addAppError, addError] + ); + + return { api: toasts, addError: _addError, addSuccess }; +}; diff --git a/x-pack/plugins/security_solution/public/common/lib/clipboard/clipboard.tsx b/x-pack/plugins/security_solution/public/common/lib/clipboard/clipboard.tsx index fdb6ed130a525..75b7308ab61f1 100644 --- a/x-pack/plugins/security_solution/public/common/lib/clipboard/clipboard.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/clipboard/clipboard.tsx @@ -4,13 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiGlobalToastListToast as Toast, EuiButtonIcon } from '@elastic/eui'; +import { EuiButtonIcon } from '@elastic/eui'; import copy from 'copy-to-clipboard'; import React from 'react'; -import uuid from 'uuid'; import * as i18n from './translations'; -import { useStateToaster } from '../../components/toasters'; +import { useAppToasts } from '../../hooks/use_app_toasts'; export type OnCopy = ({ content, @@ -20,17 +19,6 @@ export type OnCopy = ({ isSuccess: boolean; }) => void; -interface GetSuccessToastParams { - titleSummary?: string; -} - -const getSuccessToast = ({ titleSummary }: GetSuccessToastParams): Toast => ({ - id: `copy-success-${uuid.v4()}`, - color: 'success', - iconType: 'copyClipboard', - title: `${i18n.COPIED} ${titleSummary} ${i18n.TO_THE_CLIPBOARD}`, -}); - interface Props { children?: JSX.Element; content: string | number; @@ -40,7 +28,7 @@ interface Props { } export const Clipboard = ({ children, content, onCopy, titleSummary, toastLifeTimeMs }: Props) => { - const dispatchToaster = useStateToaster()[1]; + const { addSuccess } = useAppToasts(); const onClick = (event: React.MouseEvent) => { event.preventDefault(); event.stopPropagation(); @@ -52,10 +40,7 @@ export const Clipboard = ({ children, content, onCopy, titleSummary, toastLifeTi } if (isSuccess) { - dispatchToaster({ - type: 'addToaster', - toast: { toastLifeTimeMs, ...getSuccessToast({ titleSummary }) }, - }); + addSuccess(`${i18n.COPIED} ${titleSummary} ${i18n.TO_THE_CLIPBOARD}`, { toastLifeTimeMs }); } }; diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts index c3e1f35f37356..6ada887ece175 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { notificationServiceMock } from '../../../../../../../../src/core/public/mocks'; import { createKibanaContextProviderMock, createUseUiSettingMock, @@ -19,6 +20,7 @@ export const useUiSetting$ = jest.fn(createUseUiSetting$Mock()); export const useTimeZone = jest.fn(); export const useDateFormat = jest.fn(); export const useBasePath = jest.fn(() => '/test/base/path'); +export const useToasts = jest.fn(() => notificationServiceMock.createStartContract().toasts); export const useCurrentUser = jest.fn(); export const withKibana = jest.fn(createWithKibanaMock()); export const KibanaContextProvider = jest.fn(createKibanaContextProviderMock()); 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 ab442d0d09cf9..e8934259fe43e 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 @@ -6,14 +6,33 @@ import { has } from 'lodash/fp'; -export interface KibanaApiError { +export interface AppError { name: string; message: string; + body: { + message: string; + }; +} + +export interface KibanaError extends AppError { + body: { + message: string; + statusCode: number; + }; +} + +export interface SecurityAppError extends AppError { body: { message: string; status_code: number; }; } -export const isApiError = (error: unknown): error is KibanaApiError => +export const isKibanaError = (error: unknown): error is KibanaError => + has('message', error) && has('body.message', error) && has('body.statusCode', error); + +export const isSecurityAppError = (error: unknown): error is SecurityAppError => has('message', error) && has('body.message', error) && has('body.status_code', error); + +export const isAppError = (error: unknown): error is AppError => + isKibanaError(error) || isSecurityAppError(error); diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx index 0a935a9cdb1c4..d7d4be6d951b8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx @@ -23,7 +23,8 @@ import { useDeleteList, useCursor, } from '../../../shared_imports'; -import { useToasts, useKibana } from '../../../common/lib/kibana'; +import { useKibana } from '../../../common/lib/kibana'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import { GenericDownloader } from '../../../common/components/generic_downloader'; import * as i18n from './translations'; import { ValueListsTable } from './table'; @@ -45,7 +46,7 @@ export const ValueListsModalComponent: React.FC = ({ const { start: findLists, ...lists } = useFindLists(); const { start: deleteList, result: deleteResult } = useDeleteList(); const [exportListId, setExportListId] = useState(); - const toasts = useToasts(); + const { addError, addSuccess } = useAppToasts(); const fetchLists = useCallback(() => { findLists({ cursor, http, pageIndex: pageIndex + 1, pageSize }); @@ -82,21 +83,21 @@ export const ValueListsModalComponent: React.FC = ({ const handleUploadError = useCallback( (error: Error) => { if (error.name !== 'AbortError') { - toasts.addError(error, { title: i18n.UPLOAD_ERROR }); + addError(error, { title: i18n.UPLOAD_ERROR }); } }, - [toasts] + [addError] ); const handleUploadSuccess = useCallback( (response: ListSchema) => { - toasts.addSuccess({ + addSuccess({ text: i18n.uploadSuccessMessage(response.name), title: i18n.UPLOAD_SUCCESS_TITLE, }); fetchLists(); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [toasts] + [addSuccess] ); useEffect(() => { 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 65a2721013b5e..14fd9ffa50843 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 @@ -9,7 +9,7 @@ import { useEffect, useState } from 'react'; import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; import { createSignalIndex, getSignalIndex } from './api'; import * as i18n from './translations'; -import { isApiError } from '../../../../common/utils/api'; +import { isSecurityAppError } from '../../../../common/utils/api'; type Func = () => void; @@ -59,7 +59,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { signalIndexName: null, createDeSignalIndex: createIndex, }); - if (isApiError(error) && error.body.status_code !== 404) { + if (isSecurityAppError(error) && error.body.status_code !== 404) { errorToToaster({ title: i18n.SIGNAL_GET_NAME_FAILURE, error, dispatchToaster }); } } @@ -81,7 +81,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { } } catch (error) { if (isSubscribed) { - if (isApiError(error) && error.body.status_code === 409) { + if (isSecurityAppError(error) && error.body.status_code === 409) { fetchData(); } else { setSignalIndex({ diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.tsx index e21cbceeaef27..71847e7b7d8cb 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.tsx @@ -19,16 +19,16 @@ export interface UseListsConfigReturn { } export const useListsConfig = (): UseListsConfigReturn => { - const { createIndex, createIndexError, indexExists, loading: indexLoading } = useListsIndex(); + const { createIndex, indexExists, loading: indexLoading, error: indexError } = useListsIndex(); const { canManageIndex, canWriteIndex, loading: privilegesLoading } = useListsPrivileges(); const { lists } = useKibana().services; const enabled = lists != null; const loading = indexLoading || privilegesLoading; const needsIndex = indexExists === false; - const indexCreationFailed = createIndexError != null; + const hasIndexError = indexError != null; const needsIndexConfiguration = - needsIndex && (canManageIndex === false || (canManageIndex === true && indexCreationFailed)); + needsIndex && (canManageIndex === false || (canManageIndex === true && hasIndexError)); const needsConfiguration = !enabled || canWriteIndex === false || needsIndexConfiguration; useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_index.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_index.tsx index 75f12bd07d3ae..ee1316eb8a1fd 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_index.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_index.tsx @@ -7,28 +7,24 @@ import { useEffect, useState, useCallback } from 'react'; import { useReadListIndex, useCreateListIndex } from '../../../../shared_imports'; -import { useHttp, useToasts, useKibana } from '../../../../common/lib/kibana'; -import { isApiError } from '../../../../common/utils/api'; +import { useHttp, useKibana } from '../../../../common/lib/kibana'; +import { isSecurityAppError } from '../../../../common/utils/api'; import * as i18n from './translations'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -export interface UseListsIndexState { +export interface UseListsIndexReturn { + createIndex: () => void; indexExists: boolean | null; -} - -export interface UseListsIndexReturn extends UseListsIndexState { + error: unknown; loading: boolean; - createIndex: () => void; - createIndexError: unknown; - createIndexResult: { acknowledged: boolean } | undefined; } export const useListsIndex = (): UseListsIndexReturn => { - const [state, setState] = useState({ - indexExists: null, - }); + const [indexExists, setIndexExists] = useState(null); + const [error, setError] = useState(null); const { lists } = useKibana().services; const http = useHttp(); - const toasts = useToasts(); + const { addError } = useAppToasts(); const { loading: readLoading, start: readListIndex, ...readListIndexState } = useReadListIndex(); const { loading: createLoading, @@ -51,18 +47,17 @@ export const useListsIndex = (): UseListsIndexReturn => { // initial read list useEffect(() => { - if (!readLoading && state.indexExists === null) { + if (!readLoading && !error && indexExists === null) { readIndex(); } - }, [readIndex, readLoading, state.indexExists]); + }, [error, indexExists, readIndex, readLoading]); // handle read result useEffect(() => { if (readListIndexState.result != null) { - setState({ - indexExists: - readListIndexState.result.list_index && readListIndexState.result.list_item_index, - }); + setIndexExists( + readListIndexState.result.list_index && readListIndexState.result.list_item_index + ); } }, [readListIndexState.result]); @@ -75,34 +70,30 @@ export const useListsIndex = (): UseListsIndexReturn => { // handle read error useEffect(() => { - const error = readListIndexState.error; - if (isApiError(error)) { - setState({ indexExists: false }); - if (error.body.status_code !== 404) { - toasts.addError(error, { - title: i18n.LISTS_INDEX_FETCH_FAILURE, - toastMessage: error.body.message, - }); + const err = readListIndexState.error; + if (err != null) { + if (isSecurityAppError(err) && err.body.status_code === 404) { + setIndexExists(false); + } else { + setError(err); + addError(err, { title: i18n.LISTS_INDEX_FETCH_FAILURE }); } } - }, [readListIndexState.error, toasts]); + }, [addError, readListIndexState.error]); // handle create error useEffect(() => { - const error = createListIndexState.error; - if (isApiError(error)) { - toasts.addError(error, { - title: i18n.LISTS_INDEX_CREATE_FAILURE, - toastMessage: error.body.message, - }); + const err = createListIndexState.error; + if (err != null) { + setError(err); + addError(err, { title: i18n.LISTS_INDEX_CREATE_FAILURE }); } - }, [createListIndexState.error, toasts]); + }, [addError, createListIndexState.error]); return { - loading, createIndex, - createIndexError: createListIndexState.error, - createIndexResult: createListIndexState.result, - ...state, + error, + indexExists, + loading, }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_privileges.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_privileges.tsx index fbbcff33402c3..f99f62b1948e6 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_privileges.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_privileges.tsx @@ -7,8 +7,8 @@ import { useEffect, useState, useCallback } from 'react'; import { useReadListPrivileges } from '../../../../shared_imports'; -import { useHttp, useToasts, useKibana } from '../../../../common/lib/kibana'; -import { isApiError } from '../../../../common/utils/api'; +import { useHttp, useKibana } from '../../../../common/lib/kibana'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import * as i18n from './translations'; export interface UseListsPrivilegesState { @@ -79,8 +79,8 @@ export const useListsPrivileges = (): UseListsPrivilegesReturn => { }); const { lists } = useKibana().services; const http = useHttp(); - const toasts = useToasts(); - const { loading, start: readListPrivileges, ...privilegesState } = useReadListPrivileges(); + const { addError } = useAppToasts(); + const { loading, start: readListPrivileges, ...readState } = useReadListPrivileges(); const readPrivileges = useCallback(() => { if (lists) { @@ -90,20 +90,20 @@ export const useListsPrivileges = (): UseListsPrivilegesReturn => { // initRead useEffect(() => { - if (!loading && state.isAuthenticated === null) { + if (!loading && !readState.error && state.isAuthenticated === null) { readPrivileges(); } - }, [loading, readPrivileges, state.isAuthenticated]); + }, [loading, readState.error, readPrivileges, state.isAuthenticated]); // handleReadResult useEffect(() => { - if (privilegesState.result != null) { + if (readState.result != null) { try { const { is_authenticated: isAuthenticated, lists: { index: listsPrivileges }, listItems: { index: listItemsPrivileges }, - } = privilegesState.result as ListPrivileges; + } = readState.result as ListPrivileges; setState({ isAuthenticated, @@ -114,19 +114,18 @@ export const useListsPrivileges = (): UseListsPrivilegesReturn => { setState({ isAuthenticated: null, canManageIndex: false, canWriteIndex: false }); } } - }, [privilegesState.result]); + }, [readState.result]); // handleReadError useEffect(() => { - const error = privilegesState.error; - if (isApiError(error)) { - setState({ isAuthenticated: null, canManageIndex: false, canWriteIndex: false }); - toasts.addError(error, { + const error = readState.error; + if (error != null) { + setState({ isAuthenticated: false, canManageIndex: false, canWriteIndex: false }); + addError(error, { title: i18n.LISTS_PRIVILEGES_READ_FAILURE, - toastMessage: error.body.message, }); } - }, [privilegesState.error, toasts]); + }, [addError, readState.error]); return { loading, ...state }; };