diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx index c004103943afb..423fc7be9e426 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx @@ -8,10 +8,13 @@ import React, { memo, useCallback, useMemo, useState, useEffect } from 'react'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; -import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiButton, EuiFlexGroup, EuiSpacer, EuiText } from '@elastic/eui'; import type { EuiFlyoutSize } from '@elastic/eui/src/components/flyout/flyout'; import { useLocation } from 'react-router-dom'; import { useIsMounted } from '@kbn/securitysolution-hook-utils'; +import { HeaderMenu } from '@kbn/securitysolution-exception-list-components'; +import { useApi } from '@kbn/securitysolution-list-hooks'; +import { AutoDownload } from '../../../common/components/auto_download/auto_download'; import type { ServerApiError } from '../../../common/types'; import { AdministrationListPage } from '../administration_list_page'; @@ -41,10 +44,11 @@ import { useUrlParams } from '../../hooks/use_url_params'; import type { ListPageRouteState, MaybeImmutable } from '../../../../common/endpoint/types'; import { DEFAULT_EXCEPTION_LIST_ITEM_SEARCHABLE_FIELDS } from '../../../../common/endpoint/service/artifacts/constants'; import { ArtifactDeleteModal } from './components/artifact_delete_modal'; -import { useToasts } from '../../../common/lib/kibana'; +import { useKibana, useToasts } from '../../../common/lib/kibana'; import { useMemoizedRouteState } from '../../common/hooks'; import { BackToExternalAppSecondaryButton } from '../back_to_external_app_secondary_button'; import { BackToExternalAppButton } from '../back_to_external_app_button'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; type ArtifactEntryCardType = typeof ArtifactEntryCard; @@ -92,6 +96,8 @@ export const ArtifactListPage = memo( allowCardDeleteAction = true, CardDecorator, }) => { + const { services } = useKibana(); + const { http } = services; const { state: routeState } = useLocation(); const getTestId = useTestIdGenerator(dataTestSubj); const toasts = useToasts(); @@ -101,6 +107,10 @@ export const ArtifactListPage = memo( const { urlParams: { filter, includedPolicies }, } = useUrlParams(); + const { exportExceptionList } = useApi(http); + const areEndpointExceptionsMovedUnderManagementFFEnabled = useIsExperimentalFeatureEnabled( + 'endpointExceptionsMovedUnderManagement' + ); const { isPageInitializing, @@ -131,6 +141,8 @@ export const ArtifactListPage = memo( undefined | ExceptionListItemSchema >(undefined); + const [exportedData, setExportedData] = useState(); + const labels = useMemo(() => { return { ...artifactListPageLabels, @@ -238,6 +250,33 @@ export const ArtifactListPage = memo( setSelectedItemForEdit(undefined); }, []); + const handleExport = useCallback( + () => + exportExceptionList({ + id: apiClient.listId, + listId: apiClient.listId, + includeExpiredExceptions: true, + namespaceType: 'agnostic', + + onError: (exportError: Error) => + toasts?.addError(exportError, { title: labels.pageExportErrorToastTitle }), + + onSuccess: (blob) => { + setExportedData(blob); + toasts?.addSuccess(labels.pageExportSuccessToastTitle); + }, + }), + [ + exportExceptionList, + apiClient.listId, + toasts, + labels.pageExportErrorToastTitle, + labels.pageExportSuccessToastTitle, + ] + ); + + const handleOnDownload = useCallback(() => setExportedData(undefined), []); + const description = useMemo(() => { const subtitleText = labels.pageAboutInfo ? ( {labels.pageAboutInfo} @@ -267,20 +306,51 @@ export const ArtifactListPage = memo( title={labels.pageTitle} subtitle={description} actions={ - allowCardCreateAction && ( - - {labels.pageAddButtonTitle} - - ) + + {allowCardCreateAction && ( + + {labels.pageAddButtonTitle} + + )} + + {areEndpointExceptionsMovedUnderManagementFFEnabled && ( + {}, + disabled: !allowCardCreateAction, + }, + { + key: 'ExportButton', + icon: 'exportAction', + label: labels.pageExportButtonTitle, + onClick: handleExport, + }, + ]} + disableActions={isLoading} + /> + )} + } data-test-subj={getTestId('container')} > + + {isFlyoutOpened && ( { let history: AppContextTestRender['history']; let mockedApi: ReturnType; let getFirstCard: ArtifactListPageRenderingSetup['getFirstCard']; + let setExperimentalFlag: ArtifactListPageRenderingSetup['setExperimentalFlag']; beforeEach(() => { const renderSetup = getArtifactListPageRenderingSetup(); - ({ history, mockedApi, getFirstCard } = renderSetup); + ({ history, mockedApi, getFirstCard, setExperimentalFlag } = renderSetup); mockUseGetEndpointSpecificPolicies.mockReturnValue({ data: mockedApi.responseProvider.endpointPackagePolicyList(), @@ -149,6 +150,46 @@ describe('When using the ArtifactListPage component', () => { expect(getAllByText('mock decorator')).toHaveLength(10); }); + describe('Import and export', () => { + beforeEach(() => { + setExperimentalFlag({ endpointExceptionsMovedUnderManagement: true }); + }); + + it('should not show import and export actions with feature flag disabled', async () => { + setExperimentalFlag({ endpointExceptionsMovedUnderManagement: false }); + + const { queryByTestId } = await renderWithListData(); + + expect(queryByTestId('testPage-exportImportMenuButtonIcon')).not.toBeInTheDocument(); + }); + + it('should show import and export actions', async () => { + const { getByTestId } = await renderWithListData(); + + expect(getByTestId('testPage-exportImportMenuButtonIcon')).toBeInTheDocument(); + + await userEvent.click(getByTestId('testPage-exportImportMenuButtonIcon')); + expect(getByTestId('testPage-exportImportMenuActionItemImportButton')).toBeInTheDocument(); + expect(getByTestId('testPage-exportImportMenuActionItemExportButton')).toBeInTheDocument(); + }); + + it('should enable import and export buttons when user can create artifacts', async () => { + const { getByTestId } = await renderWithListData({ allowCardCreateAction: true }); + + await userEvent.click(getByTestId('testPage-exportImportMenuButtonIcon')); + expect(getByTestId('testPage-exportImportMenuActionItemImportButton')).toBeEnabled(); + expect(getByTestId('testPage-exportImportMenuActionItemExportButton')).toBeEnabled(); + }); + + it('should disable import button when user cannot create artifacts', async () => { + const { getByTestId } = await renderWithListData({ allowCardCreateAction: false }); + + await userEvent.click(getByTestId('testPage-exportImportMenuButtonIcon')); + expect(getByTestId('testPage-exportImportMenuActionItemImportButton')).toBeDisabled(); + expect(getByTestId('testPage-exportImportMenuActionItemExportButton')).toBeEnabled(); + }); + }); + describe('and interacting with card actions', () => { const clickCardAction = async (action: 'edit' | 'delete') => { await getFirstCard({ showActions: true }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/translations.ts index cbe600ae3a53f..22fd102acac6e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/translations.ts @@ -23,6 +23,30 @@ export const artifactListPageLabels = Object.freeze({ pageAddButtonTitle: i18n.translate('xpack.securitySolution.artifactListPage.addButtonTitle', { defaultMessage: 'Add artifact', }), + pageImportButtonTitle: i18n.translate( + 'xpack.securitySolution.artifactListPage.importButtonTitle', + { + defaultMessage: 'Import artifact list', + } + ), + pageExportButtonTitle: i18n.translate( + 'xpack.securitySolution.artifactListPage.exportButtonTitle', + { + defaultMessage: 'Export artifact list', + } + ), + pageExportSuccessToastTitle: i18n.translate( + 'xpack.securitySolution.artifactListPage.exportSuccessToastTitle', + { + defaultMessage: 'Artifact list exported successfully', + } + ), + pageExportErrorToastTitle: i18n.translate( + 'xpack.securitySolution.artifactListPage.exportErrorToastTitle', + { + defaultMessage: 'Artifact list export failed', + } + ), // ------------------------------ // EMPTY state labels @@ -108,6 +132,10 @@ export type ArtifactListPageRequiredLabels = Pick< | 'pageTitle' | 'pageAboutInfo' | 'pageAddButtonTitle' + | 'pageImportButtonTitle' + | 'pageExportButtonTitle' + | 'pageExportSuccessToastTitle' + | 'pageExportErrorToastTitle' | 'getShowingCountLabel' | 'cardActionEditLabel' | 'cardActionDeleteLabel' diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/blocklist/view/blocklist.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/blocklist/view/blocklist.tsx index 6515d09289f90..babbaa819ddb7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/blocklist/view/blocklist.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/blocklist/view/blocklist.tsx @@ -13,12 +13,12 @@ import { EuiLink } from '@elastic/eui'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { useHttp } from '../../../../common/lib/kibana'; -import type { ArtifactListPageProps } from '../../../components/artifact_list_page'; +import type { ArtifactListPageLabels } from '../../../components/artifact_list_page'; import { ArtifactListPage } from '../../../components/artifact_list_page'; import { BlocklistsApiClient } from '../services'; import { BlockListForm } from './components/blocklist_form'; -const BLOCKLIST_PAGE_LABELS: ArtifactListPageProps['labels'] = { +const BLOCKLIST_PAGE_LABELS: ArtifactListPageLabels = { pageTitle: i18n.translate('xpack.securitySolution.blocklist.pageTitle', { defaultMessage: 'Blocklist', }), @@ -29,6 +29,24 @@ const BLOCKLIST_PAGE_LABELS: ArtifactListPageProps['labels'] = { pageAddButtonTitle: i18n.translate('xpack.securitySolution.blocklist.pageAddButtonTitle', { defaultMessage: 'Add blocklist entry', }), + pageImportButtonTitle: i18n.translate('xpack.securitySolution.blocklist.pageImportButtonTitle', { + defaultMessage: 'Import blocklist', + }), + pageExportButtonTitle: i18n.translate('xpack.securitySolution.blocklist.pageExportButtonTitle', { + defaultMessage: 'Export blocklist', + }), + pageExportSuccessToastTitle: i18n.translate( + 'xpack.securitySolution.blocklist.pageExportSuccessToastTitle', + { + defaultMessage: 'Blocklist exported successfully', + } + ), + pageExportErrorToastTitle: i18n.translate( + 'xpack.securitySolution.blocklist.pageExportErrorToastTitle', + { + defaultMessage: 'Blocklist export failed', + } + ), getShowingCountLabel: (total) => i18n.translate('xpack.securitySolution.blocklist.showingTotal', { defaultMessage: diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_exceptions/translations.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_exceptions/translations.tsx index 68a78d62b7771..2f72007ae5359 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_exceptions/translations.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/endpoint_exceptions/translations.tsx @@ -25,6 +25,30 @@ export const ENDPOINT_EXCEPTIONS_PAGE_LABELS: ArtifactListPageLabels = { 'xpack.securitySolution.endpointExceptions.pageAddButtonTitle', { defaultMessage: 'Add endpoint exception' } ), + pageImportButtonTitle: i18n.translate( + 'xpack.securitySolution.endpointExceptions.pageImportButtonTitle', + { + defaultMessage: 'Import endpoint exception list', + } + ), + pageExportButtonTitle: i18n.translate( + 'xpack.securitySolution.endpointExceptions.pageExportButtonTitle', + { + defaultMessage: 'Export endpoint exception list', + } + ), + pageExportSuccessToastTitle: i18n.translate( + 'xpack.securitySolution.endpointExceptions.exportSuccessToastTitle', + { + defaultMessage: 'Endpoint exception list exported successfully', + } + ), + pageExportErrorToastTitle: i18n.translate( + 'xpack.securitySolution.endpointExceptions.exportErrorToastTitle', + { + defaultMessage: 'Endpoint exception list export failed', + } + ), getShowingCountLabel: (total) => i18n.translate('xpack.securitySolution.endpointExceptions.showingTotal', { defaultMessage: diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.tsx index eae0f624b0450..d9b90bd1a77ce 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.tsx @@ -13,7 +13,7 @@ import { EuiLink } from '@elastic/eui'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { useHttp } from '../../../../common/lib/kibana'; -import type { ArtifactListPageProps } from '../../../components/artifact_list_page'; +import type { ArtifactListPageLabels } from '../../../components/artifact_list_page'; import { ArtifactListPage } from '../../../components/artifact_list_page'; import { EventFiltersApiClient } from '../service/api_client'; import { EventFiltersForm } from './components/form'; @@ -50,7 +50,7 @@ export const RULE_NAME = i18n.translate('xpack.securitySolution.eventFilter.form defaultMessage: 'Endpoint Event Filtering', }); -const EVENT_FILTERS_PAGE_LABELS: ArtifactListPageProps['labels'] = { +const EVENT_FILTERS_PAGE_LABELS: ArtifactListPageLabels = { pageTitle: i18n.translate('xpack.securitySolution.eventFilters.pageTitle', { defaultMessage: 'Event Filters', }), @@ -61,6 +61,30 @@ const EVENT_FILTERS_PAGE_LABELS: ArtifactListPageProps['labels'] = { pageAddButtonTitle: i18n.translate('xpack.securitySolution.eventFilters.pageAddButtonTitle', { defaultMessage: 'Add event filter', }), + pageImportButtonTitle: i18n.translate( + 'xpack.securitySolution.eventFilters.pageImportButtonTitle', + { + defaultMessage: 'Import event filter list', + } + ), + pageExportButtonTitle: i18n.translate( + 'xpack.securitySolution.eventFilters.pageExportButtonTitle', + { + defaultMessage: 'Export event filter list', + } + ), + pageExportSuccessToastTitle: i18n.translate( + 'xpack.securitySolution.eventFilters.exportSuccessToastTitle', + { + defaultMessage: 'Event filter list exported successfully', + } + ), + pageExportErrorToastTitle: i18n.translate( + 'xpack.securitySolution.eventFilters.exportErrorToastTitle', + { + defaultMessage: 'Event filter list export failed', + } + ), getShowingCountLabel: (total) => i18n.translate('xpack.securitySolution.eventFilters.showingTotal', { defaultMessage: 'Showing {total} {total, plural, one {event filter} other {event filters}}', diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx index ebdf6c6a0be6d..5c4ada1e7bc23 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx @@ -9,13 +9,13 @@ import { i18n } from '@kbn/i18n'; import React, { memo } from 'react'; import { useHttp } from '../../../../common/lib/kibana'; import { ArtifactListPage } from '../../../components/artifact_list_page'; -import type { ArtifactListPageProps } from '../../../components/artifact_list_page'; +import type { ArtifactListPageLabels } from '../../../components/artifact_list_page'; import { HostIsolationExceptionsApiClient } from '../host_isolation_exceptions_api_client'; import { SEARCHABLE_FIELDS } from '../constants'; import { HostIsolationExceptionsForm } from './components/form'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; -const HOST_ISOLATION_EXCEPTIONS_LABELS: ArtifactListPageProps['labels'] = Object.freeze({ +const HOST_ISOLATION_EXCEPTIONS_LABELS: ArtifactListPageLabels = Object.freeze({ pageTitle: i18n.translate('xpack.securitySolution.hostIsolationExceptions.pageTitle', { defaultMessage: 'Host isolation exceptions', }), @@ -29,6 +29,30 @@ const HOST_ISOLATION_EXCEPTIONS_LABELS: ArtifactListPageProps['labels'] = Object defaultMessage: 'Add host isolation exception', } ), + pageImportButtonTitle: i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.pageImportButtonTitle', + { + defaultMessage: 'Import host isolation exception list', + } + ), + pageExportButtonTitle: i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.pageExportButtonTitle', + { + defaultMessage: 'Export host isolation exception list', + } + ), + pageExportSuccessToastTitle: i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.exportSuccessToastTitle', + { + defaultMessage: 'Host isolation exception list exported successfully', + } + ), + pageExportErrorToastTitle: i18n.translate( + 'xpack.securitySolution.hostIsolationExceptions.exportErrorToastTitle', + { + defaultMessage: 'Host isolation exception list export failed', + } + ), getShowingCountLabel: (total) => i18n.translate('xpack.securitySolution.hostIsolationExceptions.showingTotal', { defaultMessage: diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx index d29f866f8b389..aa68e17f6800e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx @@ -13,7 +13,7 @@ import { EuiLink } from '@elastic/eui'; import { useUserPrivileges } from '../../../../common/components/user_privileges'; import { useHttp } from '../../../../common/lib/kibana'; -import type { ArtifactListPageProps } from '../../../components/artifact_list_page'; +import type { ArtifactListPageLabels } from '../../../components/artifact_list_page'; import { ArtifactListPage } from '../../../components/artifact_list_page'; import { TRUSTED_PROCESS_DESCENDANTS_TAG } from '../../../../../common/endpoint/service/artifacts'; import { TrustedAppsApiClient } from '../service'; @@ -25,7 +25,7 @@ import { ProcessDescendantsIndicator } from '../../../components/artifact_entry_ import type { ArtifactEntryCardDecoratorProps } from '../../../components/artifact_entry_card/artifact_entry_card'; import { TRUSTED_APPS_PROCESS_DESCENDANT_DECORATOR_LABELS } from './translations'; -const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageProps['labels'] = { +const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageLabels = { pageTitle: i18n.translate('xpack.securitySolution.trustedApps.pageTitle', { defaultMessage: 'Trusted applications', }), @@ -36,6 +36,30 @@ const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageProps['labels'] = { pageAddButtonTitle: i18n.translate('xpack.securitySolution.trustedApps.pageAddButtonTitle', { defaultMessage: 'Add trusted application', }), + pageImportButtonTitle: i18n.translate( + 'xpack.securitySolution.trustedApps.pageImportButtonTitle', + { + defaultMessage: 'Import trusted application list', + } + ), + pageExportButtonTitle: i18n.translate( + 'xpack.securitySolution.trustedApps.pageExportButtonTitle', + { + defaultMessage: 'Export trusted application list', + } + ), + pageExportSuccessToastTitle: i18n.translate( + 'xpack.securitySolution.trustedApps.pageExportSuccessToastTitle', + { + defaultMessage: 'Trusted application list exported successfully', + } + ), + pageExportErrorToastTitle: i18n.translate( + 'xpack.securitySolution.trustedApps.pageExportErrorToastTitle', + { + defaultMessage: 'Trusted application list export failed', + } + ), getShowingCountLabel: (total) => i18n.translate('xpack.securitySolution.trustedApps.showingTotal', { defaultMessage: diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/trusted_devices_list.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/trusted_devices_list.tsx index 1d1be68a034aa..75113bd0b6c1f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/trusted_devices_list.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/trusted_devices/view/trusted_devices_list.tsx @@ -10,7 +10,10 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import type { DocLinks } from '@kbn/doc-links'; import { EuiLink } from '@elastic/eui'; -import type { ArtifactListPageProps } from '../../../components/artifact_list_page'; +import type { + ArtifactListPageProps, + ArtifactListPageLabels, +} from '../../../components/artifact_list_page'; import { ArtifactListPage } from '../../../components/artifact_list_page'; import { TrustedDevicesApiClient } from '../service/api_client'; import { TrustedDevicesForm } from './components/form'; @@ -25,7 +28,7 @@ type TrustedDevicesListProps = Omit< 'apiClient' | 'ArtifactFormComponent' | 'labels' | 'data-test-subj' >; -const TRUSTED_DEVICES_PAGE_LABELS: ArtifactListPageProps['labels'] = { +const TRUSTED_DEVICES_PAGE_LABELS: ArtifactListPageLabels = { pageTitle: i18n.translate('xpack.securitySolution.trustedDevices.list.pageTitle', { defaultMessage: 'Trusted devices', }), @@ -39,6 +42,30 @@ const TRUSTED_DEVICES_PAGE_LABELS: ArtifactListPageProps['labels'] = { defaultMessage: 'Add trusted device', } ), + pageImportButtonTitle: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.pageImportButtonTitle', + { + defaultMessage: 'Import trusted device list', + } + ), + pageExportButtonTitle: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.pageExportButtonTitle', + { + defaultMessage: 'Export trusted device list', + } + ), + pageExportSuccessToastTitle: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.pageExportSuccessToastTitle', + { + defaultMessage: 'Trusted device list exported successfully', + } + ), + pageExportErrorToastTitle: i18n.translate( + 'xpack.securitySolution.trustedDevices.list.pageExportErrorToastTitle', + { + defaultMessage: 'Trusted device list export failed', + } + ), getShowingCountLabel: (total) => i18n.translate('xpack.securitySolution.trustedDevices.list.showingTotal', { defaultMessage: