From 11fcb942ca8967ca07fd871481abfe64b62a98d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Mon, 16 Mar 2026 18:27:09 +0100
Subject: [PATCH 01/15] add client side list_id parser utility
---
.../utils/exception_list_items/index.ts | 2 ++
.../exception_list_items/list_id_parser.ts | 32 +++++++++++++++++++
2 files changed, 34 insertions(+)
create mode 100644 x-pack/solutions/security/plugins/security_solution/public/common/utils/exception_list_items/list_id_parser.ts
diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/utils/exception_list_items/index.ts b/x-pack/solutions/security/plugins/security_solution/public/common/utils/exception_list_items/index.ts
index 1dc32749bc6d5..b032b6982eee9 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/common/utils/exception_list_items/index.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/common/utils/exception_list_items/index.ts
@@ -10,3 +10,5 @@ export {
entriesToConditionEntriesMap,
entriesToConditionEntries,
} from './mappers';
+
+export { parseListIdsFromImportedFile } from './list_id_parser';
diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/utils/exception_list_items/list_id_parser.ts b/x-pack/solutions/security/plugins/security_solution/public/common/utils/exception_list_items/list_id_parser.ts
new file mode 100644
index 0000000000000..6c6b9ef4b8497
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/common/utils/exception_list_items/list_id_parser.ts
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+/**
+ * Helper function to extract list ids from an imported file. Does not check whether
+ * the file is valid, just tries to find list_id fields, so it can be used on UI side
+ * as a pre-check to ensure only the correct artifact type is being imported.
+ *
+ * @param file {File} file to extract list ids from
+ * @returns {Promise>} set of list ids found in the file
+ */
+export const parseListIdsFromImportedFile = async (file: File): Promise> =>
+ (await file.text())
+ .split('\n')
+ .filter((x) => x.trim() !== '')
+ .reduce((acc, line) => {
+ try {
+ const parsedItem = JSON.parse(line);
+
+ if (parsedItem.list_id) {
+ acc.add(parsedItem.list_id);
+ }
+ } catch (e) {
+ // ignore parsing errors, the API will handle them and return an error for the line
+ }
+
+ return acc;
+ }, new Set());
From 922df3e4496732139d47a60ca1e37337d207f0e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Mon, 16 Mar 2026 18:29:09 +0100
Subject: [PATCH 02/15] do not allow endpoint artifact import on shared list
page
---
.../import_exceptions_list_flyout/index.tsx | 36 +++++++++++++++++--
.../exceptions/translations/shared_list.ts | 8 +++++
2 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx
index 3cb6c51e16394..66413f550d39a 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx
@@ -34,10 +34,15 @@ import type {
BulkErrorSchema,
ImportExceptionsResponseSchema,
} from '@kbn/securitysolution-io-ts-list-types';
-import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
+import {
+ ENDPOINT_ARTIFACT_LIST_IDS,
+ ENDPOINT_ARTIFACT_LISTS,
+} from '@kbn/securitysolution-list-constants';
import type { HttpSetup } from '@kbn/core-http-browser';
import type { ToastInput, Toast, ErrorToastOptions } from '@kbn/core-notifications-browser';
+import { parseListIdsFromImportedFile } from '../../../common/utils/exception_list_items';
+import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import { useImportExceptionList } from '../../hooks/use_import_exception_list';
import * as i18n from '../../translations';
@@ -65,6 +70,9 @@ export const ImportExceptionListFlyout = React.memo(
const [asNewList, setAsNewList] = useState(false);
const [alreadyExistingItem, setAlreadyExistingItem] = useState(false);
const [endpointListImporting, setEndpointListImporting] = useState(false);
+ const isEndpointExceptionsMovedFFEnabled = useIsExperimentalFeatureEnabled(
+ 'endpointExceptionsMovedUnderManagement'
+ );
const resetForm = useCallback(() => {
if (filePickerRef.current?.fileInput) {
@@ -80,8 +88,21 @@ export const ImportExceptionListFlyout = React.memo(
const { start: importExceptionList, ...importExceptionListState } = useImportExceptionList();
const ctrl = useRef(new AbortController());
- const handleImportExceptionList = useCallback(() => {
+ const handleImportExceptionList = useCallback(async () => {
if (!importExceptionListState.loading && files) {
+ if (isEndpointExceptionsMovedFFEnabled) {
+ for (const file of Array.from(files)) {
+ const listIds = await parseListIdsFromImportedFile(file);
+
+ if (ENDPOINT_ARTIFACT_LIST_IDS.some((id) => listIds.has(id))) {
+ addError(new Error(i18n.IMPORT_ENDPOINT_ARTIFACTS_ERROR_TEXT), {
+ title: i18n.UPLOAD_ERROR,
+ });
+ return;
+ }
+ }
+ }
+
ctrl.current = new AbortController();
Array.from(files).forEach((file) =>
@@ -95,7 +116,16 @@ export const ImportExceptionListFlyout = React.memo(
})
);
}
- }, [asNewList, files, http, importExceptionList, importExceptionListState.loading, overwrite]);
+ }, [
+ importExceptionListState.loading,
+ files,
+ isEndpointExceptionsMovedFFEnabled,
+ addError,
+ importExceptionList,
+ http,
+ overwrite,
+ asNewList,
+ ]);
const handleImportSuccess = useCallback(
(response: ImportExceptionsResponseSchema) => {
diff --git a/x-pack/solutions/security/plugins/security_solution/public/exceptions/translations/shared_list.ts b/x-pack/solutions/security/plugins/security_solution/public/exceptions/translations/shared_list.ts
index 5d7c8e7163964..0d66f0f656910 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/exceptions/translations/shared_list.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/exceptions/translations/shared_list.ts
@@ -213,6 +213,14 @@ export const IMPORT_EXCEPTION_LIST_AS_NEW_LIST = i18n.translate(
}
);
+export const IMPORT_ENDPOINT_ARTIFACTS_ERROR_TEXT = i18n.translate(
+ 'xpack.securitySolution.exceptionsTable.importEndpointArtifactsErrorText',
+ {
+ defaultMessage:
+ 'On this page only shared exception lists can be imported, but at least one file contains Endpoint artifacts. Endpoint artifacts can be imported on their respective pages.',
+ }
+);
+
export const IMPORT_EXCEPTION_ENDPOINT_LIST_WARNING = i18n.translate(
'xpack.securitySolution.exceptionsTable.importExceptionEndpointListWarning',
{
From b6383db29d9f1d636a32e553d426b212dee23bc9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Mon, 16 Mar 2026 18:29:56 +0100
Subject: [PATCH 03/15] disable unused code with feature flag
for easier cleanup when we remove the FF
---
.../import_exceptions_list_flyout/index.tsx | 28 +++++++++++++++----
1 file changed, 22 insertions(+), 6 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx
index 66413f550d39a..ab66e3d30d9b8 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx
@@ -167,6 +167,7 @@ export const ImportExceptionListFlyout = React.memo(
if (err.error.message.includes('already exists')) {
setAlreadyExistingItem(true);
if (
+ !isEndpointExceptionsMovedFFEnabled &&
err.error.message.includes(
`Found that list_id: "${ENDPOINT_ARTIFACT_LISTS.endpointExceptions.id}" already exists`
)
@@ -187,7 +188,9 @@ export const ImportExceptionListFlyout = React.memo(
importExceptionListState.loading,
importExceptionListState?.result,
importExceptionListState?.result?.errors,
+ isEndpointExceptionsMovedFFEnabled,
]);
+
const handleFileChange = useCallback((inputFiles: FileList | null) => {
setFiles(inputFiles ?? null);
}, []);
@@ -235,22 +238,35 @@ export const ImportExceptionListFlyout = React.memo(
setAsNewList(false);
}}
/>
-
+ {isEndpointExceptionsMovedFFEnabled ? (
{
setAsNewList(!asNewList);
setOverwrite(false);
}}
/>
-
+ ) : (
+
+ {
+ setAsNewList(!asNewList);
+ setOverwrite(false);
+ }}
+ />
+
+ )}
>
)}
From 108c06f06e39085c9b3ae83ceb4de6b9ae40c829 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Mon, 16 Mar 2026 18:31:33 +0100
Subject: [PATCH 04/15] allow only given artifact import on endpoint artifact
flyout
---
.../artifact_import_flyout.test.tsx | 44 ++++++++++++++++---
.../components/artifact_import_flyout.tsx | 14 +++++-
.../components/artifact_list_page/mocks.tsx | 8 +++-
.../artifact_list_page/translations.ts | 9 +++-
.../pages/blocklist/view/blocklist.tsx | 6 +++
.../endpoint_exceptions/translations.tsx | 6 +++
.../event_filters/view/event_filters_list.tsx | 6 +++
.../view/host_isolation_exceptions_list.tsx | 6 +++
.../trusted_apps/view/trusted_apps_list.tsx | 6 +++
.../view/trusted_devices_list.tsx | 6 +++
10 files changed, 100 insertions(+), 11 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
index 83d265050c53e..427f6e1b1f853 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
@@ -31,6 +31,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
let mockedTrustedAppApi: ReturnType;
let props: ArtifactImportFlyoutProps;
let ui: ReturnType;
+ let currentListId: string;
beforeEach(() => {
const mockedContext = createAppRootMockRenderer();
@@ -38,9 +39,12 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
mockedTrustedAppApi = trustedAppsAllHttpMocks(coreStart.http);
+ const apiClient = new TrustedAppsApiClient(coreStart.http);
+ currentListId = apiClient.listId;
+
props = {
labels: artifactListPageLabels,
- apiClient: new TrustedAppsApiClient(coreStart.http),
+ apiClient,
onCancel: jest.fn(),
onSuccess: jest.fn(),
};
@@ -80,7 +84,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
it('should enable `Import` button when a file is selected', async () => {
await render();
- await ui.uploadFile();
+ await ui.uploadFile([currentListId]);
expect(ui.getImportButton()).toBeEnabled();
});
@@ -88,7 +92,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
it('should call the import API when `Import` button is clicked', async () => {
await render();
- await ui.uploadFile();
+ await ui.uploadFile([currentListId]);
await userEvent.click(ui.getImportButton());
expect(mockedTrustedAppApi.responseProvider.trustedAppImportList).toHaveBeenCalledWith(
@@ -107,7 +111,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
await render();
- await ui.uploadFile();
+ await ui.uploadFile([currentListId]);
await userEvent.click(ui.getImportButton());
expect(ui.getImportButton()).toBeDisabled();
@@ -116,7 +120,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
it('should show a success toast and call `onSuccess` after a successful import', async () => {
await render();
- await ui.uploadFile();
+ await ui.uploadFile([currentListId]);
await userEvent.click(ui.getImportButton());
expect(coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({
@@ -127,6 +131,34 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
expect(props.onSuccess).toHaveBeenCalled();
});
+ it('should show an error toast if another list is being imported', async () => {
+ await render();
+
+ await ui.uploadFile(['some-other-list-id']);
+ await userEvent.click(ui.getImportButton());
+
+ expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
+ expect.objectContaining(
+ new Error(artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError)
+ ),
+ { title: artifactListPageLabels.pageImportErrorToastTitle }
+ );
+ });
+
+ it('should show an error toast if not only the current artifact type is included in the import file', async () => {
+ await render();
+
+ await ui.uploadFile(['some-other-list-id', currentListId]);
+ await userEvent.click(ui.getImportButton());
+
+ expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
+ expect.objectContaining(
+ new Error(artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError)
+ ),
+ { title: artifactListPageLabels.pageImportErrorToastTitle }
+ );
+ });
+
it('should show an error toast if the import API fails', async () => {
mockedTrustedAppApi.responseProvider.trustedAppImportList.mockImplementation(() => {
throw new Error('Fail message from server');
@@ -134,7 +166,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
await render();
- await ui.uploadFile();
+ await ui.uploadFile([currentListId]);
await userEvent.click(ui.getImportButton());
expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
index 4d0cb0edf1624..5981728a70953 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
@@ -21,6 +21,7 @@ import {
useGeneratedHtmlId,
} from '@elastic/eui';
import React, { useCallback } from 'react';
+import { parseListIdsFromImportedFile } from '../../../../common/utils/exception_list_items';
import { useToasts } from '../../../../common/lib/kibana';
import type { ArtifactListPageLabels } from '../translations';
import { useImportArtifactList } from '../../../hooks/artifacts/use_import_artifact_list';
@@ -49,8 +50,17 @@ export const ArtifactImportFlyout: React.FC = ({
const { isLoading, mutate } = useImportArtifactList(apiClient);
- const handleOnSubmit = useCallback(() => {
+ const handleOnSubmit = useCallback(async () => {
if (file !== null) {
+ const listIds = await parseListIdsFromImportedFile(file);
+
+ if (listIds.size > 1 || !listIds.has(apiClient.listId)) {
+ toasts.addError(new Error(labels.pageImportOnlyCurrentArtifactCanBeImportedError), {
+ title: labels.pageImportErrorToastTitle,
+ });
+ return;
+ }
+
mutate(
{ file },
{
@@ -82,7 +92,7 @@ export const ArtifactImportFlyout: React.FC = ({
}
);
}
- }, [file, labels, mutate, onSuccess, toasts]);
+ }, [apiClient.listId, file, labels, mutate, onSuccess, toasts]);
const handleOnFileChange: EuiFilePickerProps['onChange'] = useCallback(
(files: FileList | null) => {
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx
index a9aefa1e40168..84d2da46e7dd1 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx
@@ -169,10 +169,14 @@ export const getArtifactImportFlyoutUiMocks = (
const getCancelButton = () => renderResult.getByTestId(`${dataTestSubj}-cancelButton`);
const getImportButton = () => renderResult.getByTestId(`${dataTestSubj}-importButton`);
- const uploadFile = () =>
+ const uploadFile = (listIds: string[]) =>
userEvent.upload(
renderResult.getByTestId(`${dataTestSubj}-filePicker`),
- new File(['random file content'], 'trusted_apps.ndjson')
+ new File(
+ // every id is duplicated to simulate multiple lines. plus one invalid line to make sure parsing errors are ignored
+ [listIds.map((id) => `{"list_id":"${id}"}\n{"list_id":"${id}"}\ninvalid line`).join('\n')],
+ 'trusted_apps.ndjson'
+ )
);
return { queryImportFlyout, getCancelButton, getImportButton, uploadFile };
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 2307fd6f13c42..bff004b3eca68 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
@@ -65,6 +65,12 @@ export const artifactListPageLabels = Object.freeze({
defaultMessage: 'Artifact list import failed',
}
),
+ pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importOnlyCurrentArtifactCanBeImportedToastMessage',
+ {
+ defaultMessage: 'Only the current artifact type can be imported on this page.',
+ }
+ ),
importFlyoutDetails: i18n.translate(
'xpack.securitySolution.artifactListPage.importFlyoutDetails',
{
@@ -141,7 +147,7 @@ export const artifactListPageLabels = Object.freeze({
cardActionDeleteLabel: i18n.translate(
'xpack.securitySolution.artifactListPage.cardActionDeleteLabel',
{
- defaultMessage: 'Delete event filter',
+ defaultMessage: 'Delete artifact',
}
),
@@ -173,6 +179,7 @@ export type ArtifactListPageRequiredLabels = Pick<
| 'pageExportErrorToastTitle'
| 'pageImportSuccessToastTitle'
| 'pageImportErrorToastTitle'
+ | 'pageImportOnlyCurrentArtifactCanBeImportedError'
| '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 54269a6a191f7..abfd6410b8656 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
@@ -59,6 +59,12 @@ const BLOCKLIST_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Blocklist import failed',
}
),
+ pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
+ 'xpack.securitySolution.blocklist.pageImportOnlyCurrentArtifactCanBeImportedError',
+ {
+ defaultMessage: 'Only blocklist can be imported on this page.',
+ }
+ ),
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 d58bbb82837b6..0270b621bbd9e 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
@@ -61,6 +61,12 @@ export const ENDPOINT_EXCEPTIONS_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Endpoint exception list import failed',
}
),
+ pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
+ 'xpack.securitySolution.endpointExceptions.pageImportOnlyCurrentArtifactCanBeImportedError',
+ {
+ defaultMessage: 'Only endpoint exception list can be imported on this page.',
+ }
+ ),
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 b52010aa45596..91556af0d95b9 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
@@ -97,6 +97,12 @@ const EVENT_FILTERS_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Event filter list import failed',
}
),
+ pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
+ 'xpack.securitySolution.eventFilters.pageImportOnlyCurrentArtifactCanBeImportedError',
+ {
+ defaultMessage: 'Only event filter list can be imported on this page.',
+ }
+ ),
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 215c3cca1c8ca..748b5934a72e4 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
@@ -65,6 +65,12 @@ const HOST_ISOLATION_EXCEPTIONS_LABELS: ArtifactListPageLabels = Object.freeze({
defaultMessage: 'Host isolation exception list import failed',
}
),
+ pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
+ 'xpack.securitySolution.hostIsolationExceptions.pageImportOnlyCurrentArtifactCanBeImportedError',
+ {
+ defaultMessage: 'Only host isolation exception list can be imported on this page.',
+ }
+ ),
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 fe6fd2174184e..ce9e00a57c385 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
@@ -72,6 +72,12 @@ const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Trusted application list import failed',
}
),
+ pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
+ 'xpack.securitySolution.trustedApps.pageImportOnlyCurrentArtifactCanBeImportedError',
+ {
+ defaultMessage: 'Only trusted application list can be imported on this page.',
+ }
+ ),
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 dbcd38a1c58b6..48201b1a11a4a 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
@@ -78,6 +78,12 @@ const TRUSTED_DEVICES_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Trusted device list import failed',
}
),
+ pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
+ 'xpack.securitySolution.trustedDevices.list.pageImportOnlyCurrentArtifactCanBeImportedError',
+ {
+ defaultMessage: 'Only trusted device list can be imported on this page.',
+ }
+ ),
getShowingCountLabel: (total) =>
i18n.translate('xpack.securitySolution.trustedDevices.list.showingTotal', {
defaultMessage:
From 832d5c5f04a5df0b8bba9a29d167d2028a3cc225 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Tue, 17 Mar 2026 18:26:18 +0100
Subject: [PATCH 05/15] fix jest integration test
---
.../integration_tests/artifact_list_page.test.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx
index 740cf9e422fd4..14d61bd9fabdc 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx
@@ -20,6 +20,7 @@ import {
import { getDeferred } from '../../../mocks/utils';
import { useGetEndpointSpecificPolicies } from '../../../services/policies/hooks';
import type { ArtifactEntryCardDecoratorProps } from '../../artifact_entry_card';
+import { ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
jest.mock('../../../services/policies/hooks', () => ({
useGetEndpointSpecificPolicies: jest.fn(),
@@ -231,7 +232,7 @@ describe('When using the ArtifactListPage component', () => {
await userEvent.click(importExportUi.getMenuButton());
await userEvent.click(importExportUi.getImportButton());
- await importFlyoutUi.uploadFile();
+ await importFlyoutUi.uploadFile([ENDPOINT_ARTIFACT_LISTS.trustedApps.id]);
const currentApiCallCount = mockedApi.responseProvider.trustedAppsList.mock.calls.length;
await userEvent.click(importFlyoutUi.getImportButton());
From fc3315faeae06ffd85cb626e18d70030d846d5c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Tue, 31 Mar 2026 15:35:46 +0200
Subject: [PATCH 06/15] switch behavior to APPEND instead of OVERWRITE
---
.../components/artifact_import_flyout.test.tsx | 2 +-
.../hooks/artifacts/use_import_artifact_list.test.tsx | 5 +++--
.../exceptions_list/exceptions_list_api_client.test.ts | 3 ++-
.../services/exceptions_list/exceptions_list_api_client.ts | 3 ++-
4 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
index 427f6e1b1f853..b5f2321e44703 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
@@ -98,7 +98,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
expect(mockedTrustedAppApi.responseProvider.trustedAppImportList).toHaveBeenCalledWith(
expect.objectContaining({
version: '2023-10-31',
- query: { overwrite: true } as HttpFetchOptionsWithPath['query'],
+ query: { overwrite: false, as_new_list: false } as HttpFetchOptionsWithPath['query'],
})
);
});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/hooks/artifacts/use_import_artifact_list.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/hooks/artifacts/use_import_artifact_list.test.tsx
index 8447b5e667723..378b8f2537ccf 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/hooks/artifacts/use_import_artifact_list.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/hooks/artifacts/use_import_artifact_list.test.tsx
@@ -69,13 +69,14 @@ describe('Import artifact list hook', () => {
headers: { 'Content-Type': undefined },
body: expect.any(FormData),
query: {
- overwrite: true,
+ overwrite: false,
+ as_new_list: false,
},
})
);
});
- it('should throw when importing an artifact list', async () => {
+ it('should throw when server responds with error', async () => {
const expectedError = {
response: {
status: 500,
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.test.ts b/x-pack/solutions/security/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.test.ts
index b5a5a75294fdf..4e19f54035930 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.test.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.test.ts
@@ -275,7 +275,8 @@ describe('Exceptions List Api Client', () => {
headers: { 'Content-Type': undefined },
body: expect.any(FormData),
query: {
- overwrite: true,
+ overwrite: false,
+ as_new_list: false,
},
});
});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.ts b/x-pack/solutions/security/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.ts
index f8cb56e4e8544..728f971742787 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/services/exceptions_list/exceptions_list_api_client.ts
@@ -319,7 +319,8 @@ export class ExceptionsListApiClient {
body: formData,
headers: { 'Content-Type': undefined },
query: {
- overwrite: true,
+ overwrite: false,
+ as_new_list: false,
} as ImportExceptionListRequestQuery,
});
}
From d2ae717b6cb71218fc6436701dc2eb22753e6b92 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Tue, 31 Mar 2026 16:33:57 +0200
Subject: [PATCH 07/15] add positive friction (confirm modal) to import process
---
.../artifact_import_confirm_modal.tsx | 83 +++++++++++++++++++
.../artifact_import_flyout.test.tsx | 36 +++++++-
.../components/artifact_import_flyout.tsx | 25 +++++-
.../components/artifact_list_page/mocks.tsx | 15 +++-
4 files changed, 154 insertions(+), 5 deletions(-)
create mode 100644 x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_confirm_modal.tsx
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_confirm_modal.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_confirm_modal.tsx
new file mode 100644
index 0000000000000..815e82087aa50
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_confirm_modal.tsx
@@ -0,0 +1,83 @@
+/*
+ * 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 React from 'react';
+import { i18n } from '@kbn/i18n';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiText,
+ useGeneratedHtmlId,
+} from '@elastic/eui';
+import { useTestIdGenerator } from '../../../hooks/use_test_id_generator';
+
+interface ArtifactImportConfirmModalProps {
+ onCancel: () => void;
+ onConfirm: () => void;
+ isLoading: boolean;
+ 'data-test-subj'?: string;
+}
+
+export const ArtifactImportConfirmModal: React.FC = ({
+ onCancel,
+ onConfirm,
+ isLoading,
+ 'data-test-subj': dataTestSubj = 'artifactImportConfirmModal',
+}) => {
+ const getTestId = useTestIdGenerator(dataTestSubj);
+ const modalTitleId = useGeneratedHtmlId();
+
+ return (
+
+ {' '}
+
+
+ {i18n.translate('xpack.securitySolution.artifactListPage.importConfirmModal.title', {
+ defaultMessage: 'Import artifacts?',
+ })}
+
+
+
+
+
+ {i18n.translate('xpack.securitySolution.artifactListPage.importConfirmModal.info', {
+ defaultMessage:
+ 'This will add new artifacts to your list. If an imported artifact already exists, the current version will be kept and the duplicate will be skipped.',
+ })}
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importConfirmModal.cancelButtonTitle',
+ { defaultMessage: 'Cancel' }
+ )}
+
+
+
+ {i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importConfirmModal.confirmButtonTitle',
+ { defaultMessage: 'Import' }
+ )}
+
+
+
+ );
+};
+
+ArtifactImportConfirmModal.displayName = 'ArtifactImportConfirmModal';
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
index b5f2321e44703..0947d0a7a4f43 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
@@ -89,12 +89,33 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
expect(ui.getImportButton()).toBeEnabled();
});
- it('should call the import API when `Import` button is clicked', async () => {
+ it('should show a confirmation modal when `Import` button is clicked', async () => {
await render();
await ui.uploadFile([currentListId]);
await userEvent.click(ui.getImportButton());
+ expect(ui.queryConfirmModal()).toBeInTheDocument();
+ });
+
+ it("should close the confirmation modal but keep the flyout open when the modal's cancel button is clicked", async () => {
+ await render();
+
+ await ui.uploadFile([currentListId]);
+ await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalCancelButton());
+
+ expect(ui.queryConfirmModal()).not.toBeInTheDocument();
+ expect(ui.queryImportFlyout()).toBeInTheDocument();
+ });
+
+ it('should call the import API when the modal is confirmed', async () => {
+ await render();
+
+ await ui.uploadFile([currentListId]);
+ await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
+
expect(mockedTrustedAppApi.responseProvider.trustedAppImportList).toHaveBeenCalledWith(
expect.objectContaining({
version: '2023-10-31',
@@ -103,7 +124,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
);
});
- it('should disable `Import` button while the import is in progress', async () => {
+ it('should disable `Import` button on the modal and the flyout while the import is in progress', async () => {
const deferrable = getDeferred();
mockedTrustedAppApi.responseProvider.trustedAppImportList.mockDelay.mockReturnValue(
deferrable.promise
@@ -113,8 +134,10 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
await ui.uploadFile([currentListId]);
await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
expect(ui.getImportButton()).toBeDisabled();
+ expect(ui.getConfirmModalConfirmButton()).toBeDisabled();
});
it('should show a success toast and call `onSuccess` after a successful import', async () => {
@@ -122,6 +145,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
await ui.uploadFile([currentListId]);
await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
expect(coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({
text: '2 artifacts imported.',
@@ -131,11 +155,12 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
expect(props.onSuccess).toHaveBeenCalled();
});
- it('should show an error toast if another list is being imported', async () => {
+ it('should show an error toast and close the modal if another list is being imported', async () => {
await render();
await ui.uploadFile(['some-other-list-id']);
await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
expect.objectContaining(
@@ -143,6 +168,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
),
{ title: artifactListPageLabels.pageImportErrorToastTitle }
);
+ expect(ui.queryConfirmModal()).not.toBeInTheDocument();
});
it('should show an error toast if not only the current artifact type is included in the import file', async () => {
@@ -150,6 +176,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
await ui.uploadFile(['some-other-list-id', currentListId]);
await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
expect.objectContaining(
@@ -157,6 +184,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
),
{ title: artifactListPageLabels.pageImportErrorToastTitle }
);
+ expect(ui.queryConfirmModal()).not.toBeInTheDocument();
});
it('should show an error toast if the import API fails', async () => {
@@ -168,10 +196,12 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
await ui.uploadFile([currentListId]);
await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
expect.objectContaining(new Error('Fail message from server')),
{ title: 'Artifact list import failed', toastMessage: 'Fail message from server' }
);
+ expect(ui.queryConfirmModal()).not.toBeInTheDocument();
});
});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
index 5981728a70953..7c8104b419788 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
@@ -27,6 +27,7 @@ import type { ArtifactListPageLabels } from '../translations';
import { useImportArtifactList } from '../../../hooks/artifacts/use_import_artifact_list';
import type { ExceptionsListApiClient } from '../../../services/exceptions_list/exceptions_list_api_client';
import { useTestIdGenerator } from '../../../hooks/use_test_id_generator';
+import { ArtifactImportConfirmModal } from './artifact_import_confirm_modal';
export interface ArtifactImportFlyoutProps {
onCancel: () => void;
@@ -47,10 +48,19 @@ export const ArtifactImportFlyout: React.FC = ({
const getTestId = useTestIdGenerator(dataTestSubj);
const [file, setFile] = React.useState(null);
+ const [showConfirmModal, setShowConfirmModal] = React.useState(false);
const { isLoading, mutate } = useImportArtifactList(apiClient);
- const handleOnSubmit = useCallback(async () => {
+ const handleOnSubmit = useCallback(() => {
+ setShowConfirmModal(true);
+ }, []);
+
+ const handleOnCancelModal = useCallback(() => {
+ setShowConfirmModal(false);
+ }, []);
+
+ const handleOnConfirmModal = useCallback(async () => {
if (file !== null) {
const listIds = await parseListIdsFromImportedFile(file);
@@ -58,6 +68,8 @@ export const ArtifactImportFlyout: React.FC = ({
toasts.addError(new Error(labels.pageImportOnlyCurrentArtifactCanBeImportedError), {
title: labels.pageImportErrorToastTitle,
});
+ setShowConfirmModal(false);
+
return;
}
@@ -69,6 +81,7 @@ export const ArtifactImportFlyout: React.FC = ({
title: labels.pageImportErrorToastTitle,
toastMessage: error.body?.message || error.message,
});
+ setShowConfirmModal(false);
},
onSuccess: (response) => {
toasts.addSuccess({
@@ -87,6 +100,7 @@ export const ArtifactImportFlyout: React.FC = ({
}`,
toastLifeTimeMs: 60_000,
});
+ setShowConfirmModal(false);
onSuccess();
},
}
@@ -117,6 +131,15 @@ export const ArtifactImportFlyout: React.FC = ({
aria-labelledby={importEndpointArtifactListFlyoutTitleId}
data-test-subj={getTestId()}
>
+ {showConfirmModal && (
+
+ )}
+
{labels.pageImportButtonTitle}
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx
index 84d2da46e7dd1..3b7144eae3065 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/mocks.tsx
@@ -168,6 +168,11 @@ export const getArtifactImportFlyoutUiMocks = (
const queryImportFlyout = () => renderResult.queryByTestId(dataTestSubj);
const getCancelButton = () => renderResult.getByTestId(`${dataTestSubj}-cancelButton`);
const getImportButton = () => renderResult.getByTestId(`${dataTestSubj}-importButton`);
+ const queryConfirmModal = () => renderResult.queryByTestId(`${dataTestSubj}-confirmModal`);
+ const getConfirmModalConfirmButton = () =>
+ renderResult.getByTestId(`${dataTestSubj}-confirmModal-confirmButton`);
+ const getConfirmModalCancelButton = () =>
+ renderResult.getByTestId(`${dataTestSubj}-confirmModal-cancelButton`);
const uploadFile = (listIds: string[]) =>
userEvent.upload(
@@ -179,5 +184,13 @@ export const getArtifactImportFlyoutUiMocks = (
)
);
- return { queryImportFlyout, getCancelButton, getImportButton, uploadFile };
+ return {
+ queryImportFlyout,
+ getCancelButton,
+ getImportButton,
+ queryConfirmModal,
+ getConfirmModalConfirmButton,
+ getConfirmModalCancelButton,
+ uploadFile,
+ };
};
From 1f53c55d9af52bd09ebf1565243aed218fc34f2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Wed, 1 Apr 2026 11:00:07 +0200
Subject: [PATCH 08/15] update error message texts
---
.../validators/base_validator.test.ts | 30 +++++++++++++--
.../endpoint/validators/base_validator.ts | 38 +++++++++++++------
.../validators/blocklist_validator.ts | 2 +-
.../endpoint_exceptions_validator.ts | 2 +-
.../validators/event_filter_validator.ts | 2 +-
.../host_isolation_exceptions_validator.ts | 2 +-
.../endpoint/validators/mocks.ts | 4 ++
.../validators/trusted_app_validator.ts | 2 +-
.../validators/trusted_device_validator.ts | 2 +-
9 files changed, 63 insertions(+), 21 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.test.ts
index 5a12b473efe89..b948b8d84369e 100644
--- a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.test.ts
+++ b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.test.ts
@@ -654,6 +654,28 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
});
});
+ describe('#validateCanImportGlobalArtifacts()', () => {
+ beforeEach(() => {
+ exceptionLikeItem.tags = [GLOBAL_ARTIFACT_TAG];
+ });
+
+ it('should error is user does not have new global artifact management privilege', async () => {
+ authzMock.canManageGlobalArtifacts = false;
+
+ await expect(
+ validator._validateCanImportGlobalArtifacts(exceptionLikeItem)
+ ).rejects.toThrow(
+ /This artifact can't be imported because you don't have permission to manage global artifacts./
+ );
+ });
+
+ it('should allow import of global artifacts when user has privilege', async () => {
+ await expect(
+ validator._validateCanImportGlobalArtifacts(exceptionLikeItem)
+ ).resolves.toBeUndefined();
+ });
+ });
+
describe('#validateImportOwnerSpaceIds()', () => {
it('should do nothing when item has no tags', async () => {
exceptionLikeItem.tags = [];
@@ -676,7 +698,7 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
setArtifactOwnerSpaceId(exceptionLikeItem, 'inaccessible-space');
await expect(validator._validateImportOwnerSpaceIds(exceptionLikeItem)).rejects.toThrow(
- /invalid owner space IDs/
+ /This artifact can\'t be imported because it belongs to a space you don\'t have access to/
);
});
@@ -689,7 +711,7 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
]);
await expect(validator._validateImportOwnerSpaceIds(exceptionLikeItem)).rejects.toThrow(
- /Importing artifacts that are not visible in the current space/
+ /This artifact can't be imported because it isn't visible in the current space/
);
});
});
@@ -709,7 +731,7 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
setArtifactOwnerSpaceId(exceptionLikeItem, 'other-space');
await expect(validator._validateImportOwnerSpaceIds(exceptionLikeItem)).rejects.toThrow(
- /Importing artifacts to a different space requires global artifact management privilege/
+ /This artifact can't be imported because you don't have permission to manage artifacts in other spaces/
);
});
@@ -719,7 +741,7 @@ describe('When using Artifacts Exceptions BaseValidator', () => {
setArtifactOwnerSpaceId(exceptionLikeItem, 'other-space');
await expect(validator._validateImportOwnerSpaceIds(exceptionLikeItem)).rejects.toThrow(
- /Importing artifacts to a different space requires global artifact management privilege/
+ /This artifact can't be imported because you don't have permission to manage artifacts in other spaces/
);
});
});
diff --git a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts
index 472fc2853ce8e..9bea0be5b111d 100644
--- a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts
+++ b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts
@@ -47,7 +47,15 @@ const IMPORTING_TO_OTHER_SPACE_NOT_ALLOWED_MESSAGE = i18n.translate(
'xpack.securitySolution.baseValidator.importingToOtherSpaceNotAllowedMessage',
{
defaultMessage:
- 'Importing artifacts to a different space requires global artifact management privilege',
+ "This artifact can't be imported because you don't have permission to manage artifacts in other spaces. Contact your administrator for access.",
+ }
+);
+
+const GLOBAL_ARTIFACT_IMPORT_NOT_ALLOWED_MESSAGE = i18n.translate(
+ 'xpack.securitySolution.baseValidator.noGlobalArtifactImportMessage',
+ {
+ defaultMessage:
+ "This artifact can't be imported because you don't have permission to manage global artifacts. Contact your administrator for access.",
}
);
@@ -62,19 +70,18 @@ export const GLOBAL_ARTIFACT_MANAGEMENT_NOT_ALLOWED_MESSAGE = i18n.translate(
const IMPORTING_ARTIFACT_NOT_VISIBLE_IN_CURRENT_SPACE_NOT_ALLOWED_MESSAGE = i18n.translate(
'xpack.securitySolution.baseValidator.importingArtifactNotVisibleInCurrentSpace',
{
- defaultMessage: 'Importing artifacts that are not visible in the current space is not allowed',
+ defaultMessage:
+ "This artifact can't be imported because it isn't visible in the current space. Try importing it from a matching space or a space with access to the related policy.",
}
);
-const IMPORTING_ARTIFACT_WITH_INVALID_OWNER_SPACE_ID = (spaceIds: string[]): string =>
- i18n.translate('xpack.securitySolution.baseValidator.invalidOwnerSpaceId', {
+const IMPORTING_ARTIFACT_WITH_INVALID_OWNER_SPACE_ID = i18n.translate(
+ 'xpack.securitySolution.baseValidator.invalidOwnerSpaceId',
+ {
defaultMessage:
- 'Importing artifacts with invalid owner space IDs is not allowed. The following space {numberOfSpaces, plural, one {ID is} other {IDs are} } invalid or unaccessible by current user: {invalidSpaceIds}',
- values: {
- invalidSpaceIds: spaceIds.join(', '),
- numberOfSpaces: spaceIds.length,
- },
- });
+ "This artifact can't be imported because it belongs to a space you don't have access to. Update the artifact in its original space and try again.",
+ }
+);
const ITEM_CANNOT_BE_MANAGED_IN_CURRENT_SPACE_MESSAGE = (spaceIds: string[]): string =>
i18n.translate('xpack.securitySolution.baseValidator.cannotManageItemInCurrentSpace', {
@@ -342,7 +349,7 @@ export class BaseValidator {
if (invalidSpaceIds.length > 0) {
throw new EndpointArtifactExceptionValidationError(
- IMPORTING_ARTIFACT_WITH_INVALID_OWNER_SPACE_ID(invalidSpaceIds),
+ IMPORTING_ARTIFACT_WITH_INVALID_OWNER_SPACE_ID,
403
);
}
@@ -422,6 +429,15 @@ export class BaseValidator {
}
}
+ protected async validateCanImportGlobalArtifacts(item: ExceptionItemLikeOptions): Promise {
+ if (!this.isItemByPolicy(item) && !(await this.endpointAuthzPromise).canManageGlobalArtifacts) {
+ throw new EndpointArtifactExceptionValidationError(
+ `${ENDPOINT_AUTHZ_ERROR_MESSAGE}. ${GLOBAL_ARTIFACT_IMPORT_NOT_ALLOWED_MESSAGE}`,
+ 403
+ );
+ }
+ }
+
protected async validateCanUpdateItemInActiveSpace(
updatedItem: Partial>,
currentSavedItem: ExceptionListItemSchema
diff --git a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts
index 9e634b46f7c5e..607c48e0257b0 100644
--- a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts
+++ b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts
@@ -237,7 +237,7 @@ export class BlocklistValidator extends BaseValidator {
await this.validatePreImportItems(items, async (item) => {
// import specific validations
await this.validateImportOwnerSpaceIds(item); // instead of validateCreateOwnerSpaceIds
- await this.validateCanCreateGlobalArtifacts(item);
+ await this.validateCanImportGlobalArtifacts(item); // instead of validateCanCreateGlobalArtifacts
await this.removeInvalidPolicyIds(item); // instead of validateByPolicyItem
// usual validators from pre-create
diff --git a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts
index 76248351499e7..6f4c113f48bc2 100644
--- a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts
+++ b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts
@@ -49,7 +49,7 @@ export class EndpointExceptionsValidator extends BaseValidator {
await this.validatePreImportItems(items, async (item) => {
// import specific validations
await this.validateImportOwnerSpaceIds(item); // instead of validateCreateOwnerSpaceIds
- await this.validateCanCreateGlobalArtifacts(item);
+ await this.validateCanImportGlobalArtifacts(item); // instead of validateCanCreateGlobalArtifacts
await this.removeInvalidPolicyIds(item); // instead of validateByPolicyItem
// usual validators from pre-create
diff --git a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts
index 271209f458d37..e98c7055dba35 100644
--- a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts
+++ b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts
@@ -56,7 +56,7 @@ export class EventFilterValidator extends BaseValidator {
await this.validatePreImportItems(items, async (item) => {
// import specific validations
await this.validateImportOwnerSpaceIds(item); // instead of validateCreateOwnerSpaceIds
- await this.validateCanCreateGlobalArtifacts(item);
+ await this.validateCanImportGlobalArtifacts(item); // instead of validateCanCreateGlobalArtifacts
await this.removeInvalidPolicyIds(item); // instead of validateByPolicyItem
// usual validators from pre-create
diff --git a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts
index c2a272400d8db..25bd178bc85d7 100644
--- a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts
+++ b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts
@@ -80,7 +80,7 @@ export class HostIsolationExceptionsValidator extends BaseValidator {
await this.validatePreImportItems(items, async (item) => {
// import specific validations
await this.validateImportOwnerSpaceIds(item); // instead of validateCreateOwnerSpaceIds
- await this.validateCanCreateGlobalArtifacts(item);
+ await this.validateCanImportGlobalArtifacts(item); // instead of validateCanCreateGlobalArtifacts
await this.removeInvalidPolicyIds(item); // instead of validateByPolicyItem
// usual validators from pre-create
diff --git a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/mocks.ts b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/mocks.ts
index 7025f7b478fa4..28c6ca11c539a 100644
--- a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/mocks.ts
+++ b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/mocks.ts
@@ -70,6 +70,10 @@ export class BaseValidatorMock extends BaseValidator {
return this.validateCanCreateGlobalArtifacts(item);
}
+ _validateCanImportGlobalArtifacts(item: ExceptionItemLikeOptions): Promise {
+ return this.validateCanImportGlobalArtifacts(item);
+ }
+
_validateCanUpdateItemInActiveSpace(
updatedItem: Partial>,
currentSavedItem: ExceptionListItemSchema
diff --git a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts
index 00d72ec85d5ef..42ec751483c27 100644
--- a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts
+++ b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts
@@ -227,7 +227,7 @@ export class TrustedAppValidator extends BaseValidator {
await this.validatePreImportItems(items, async (item) => {
// import specific validations
await this.validateImportOwnerSpaceIds(item); // instead of validateCreateOwnerSpaceIds
- await this.validateCanCreateGlobalArtifacts(item);
+ await this.validateCanImportGlobalArtifacts(item); // instead of validateCanCreateGlobalArtifacts
await this.removeInvalidPolicyIds(item); // instead of validateByPolicyItem
// usual validators from pre-create
diff --git a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_device_validator.ts b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_device_validator.ts
index 7fe07fe66ce11..c651a7a8158c9 100644
--- a/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_device_validator.ts
+++ b/x-pack/solutions/security/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_device_validator.ts
@@ -127,7 +127,7 @@ export class TrustedDeviceValidator extends BaseValidator {
await this.validatePreImportItems(items, async (item) => {
// import specific validations
await this.validateImportOwnerSpaceIds(item); // instead of validateCreateOwnerSpaceIds
- await this.validateCanCreateGlobalArtifacts(item);
+ await this.validateCanImportGlobalArtifacts(item); // instead of validateCanCreateGlobalArtifacts
await this.removeInvalidPolicyIds(item); // instead of validateByPolicyItem
// usual validators from pre-create
From ee8d864fc195ce4eff61032bfef4ba6e177411b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Wed, 1 Apr 2026 14:47:29 +0200
Subject: [PATCH 09/15] show success/warning/danger modal based on import
result
---
.../artifact_import_flyout.test.tsx | 209 ++++++++++++++----
.../components/artifact_import_flyout.tsx | 46 ++--
.../artifact_list_page/translations.ts | 22 +-
3 files changed, 204 insertions(+), 73 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
index 0947d0a7a4f43..0f3650f144826 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
@@ -140,68 +140,179 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
expect(ui.getConfirmModalConfirmButton()).toBeDisabled();
});
- it('should show a success toast and call `onSuccess` after a successful import', async () => {
- await render();
+ describe('when handling server response', () => {
+ const LIST_CONFLICT_ERROR = {
+ error: {
+ status_code: 409,
+ message:
+ 'Found that list_id: "endpoint_list" already exists. Import of list_id: "endpoint_list" skipped.',
+ },
+ list_id: 'endpoint_list',
+ };
- await ui.uploadFile([currentListId]);
- await userEvent.click(ui.getImportButton());
- await userEvent.click(ui.getConfirmModalConfirmButton());
+ const ITEM_CONFLICT_ERROR = {
+ error: {
+ status_code: 409,
+ message:
+ 'Found that item_id: "0d82595f-f79d-48c8-8522-7715e1640884" already exists. Import of item_id: "0d82595f-f79d-48c8-8522-7715e1640884" skipped.',
+ },
+ list_id: 'endpoint_list',
+ item_id: '0d82595f-f79d-48c8-8522-7715e1640884',
+ };
- expect(coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({
- text: '2 artifacts imported.',
- title: 'Artifact list imported successfully',
- toastLifeTimeMs: 60_000,
+ const ITEM_ENDPOINT_ARTIFACT_ERROR = {
+ error: {
+ status_code: 403,
+ message:
+ "EndpointArtifactError: This artifact can't be imported because it belongs to a space you don't have access to. Update the artifact in its original space and try again.",
+ },
+ list_id: 'endpoint_list',
+ item_id: '1a0200db-3dd7-46f4-bb4d-f23904559c32',
+ };
+
+ it('should show a success toast and call `onSuccess` after a successful import', async () => {
+ await render();
+
+ await ui.uploadFile([currentListId]);
+ await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
+
+ expect(coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({
+ title: 'Artifacts imported',
+ text: 'All artifacts were imported successfully.',
+ toastLifeTimeMs: 60_000,
+ });
+ expect(props.onSuccess).toHaveBeenCalled();
});
- expect(props.onSuccess).toHaveBeenCalled();
- });
- it('should show an error toast and close the modal if another list is being imported', async () => {
- await render();
+ it('should not care about list conflict in response', async () => {
+ mockedTrustedAppApi.responseProvider.trustedAppImportList.mockImplementation(() => ({
+ errors: [LIST_CONFLICT_ERROR],
+ success: false,
+ success_count: 2,
+ success_exception_lists: false,
+ success_count_exception_lists: 0,
+ success_exception_list_items: true,
+ success_count_exception_list_items: 2,
+ }));
+
+ await render();
+
+ await ui.uploadFile([currentListId]);
+ await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
+
+ expect(coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({
+ title: 'Artifacts imported',
+ text: 'All artifacts were imported successfully.',
+ toastLifeTimeMs: 60_000,
+ });
+ expect(props.onSuccess).toHaveBeenCalled();
+ });
- await ui.uploadFile(['some-other-list-id']);
- await userEvent.click(ui.getImportButton());
- await userEvent.click(ui.getConfirmModalConfirmButton());
+ it('should show a warning toast when some of the items were not imported', async () => {
+ mockedTrustedAppApi.responseProvider.trustedAppImportList.mockImplementation(() => ({
+ errors: [LIST_CONFLICT_ERROR, ITEM_CONFLICT_ERROR, ITEM_ENDPOINT_ARTIFACT_ERROR],
+ success: false,
+ success_count: 3,
+ success_exception_lists: false,
+ success_count_exception_lists: 0,
+ success_exception_list_items: false,
+ success_count_exception_list_items: 3, // there are some successful imports
+ }));
+
+ await render();
+
+ await ui.uploadFile([currentListId]);
+ await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
+
+ expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledWith({
+ title: 'Import completed with errors',
+ toastLifeTimeMs: 60_000,
+ });
+ expect(props.onSuccess).toHaveBeenCalled();
+ });
- expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
- expect.objectContaining(
- new Error(artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError)
- ),
- { title: artifactListPageLabels.pageImportErrorToastTitle }
- );
- expect(ui.queryConfirmModal()).not.toBeInTheDocument();
- });
+ it('should call `showErrorList` when the `View errors` button is pressed in the warning toast', async () => {});
+
+ it('should show a danger toast when none of the items were imported', async () => {
+ mockedTrustedAppApi.responseProvider.trustedAppImportList.mockImplementation(() => ({
+ errors: [LIST_CONFLICT_ERROR, ITEM_CONFLICT_ERROR, ITEM_ENDPOINT_ARTIFACT_ERROR],
+ success: false,
+ success_count: 3,
+ success_exception_lists: false,
+ success_count_exception_lists: 0,
+ success_exception_list_items: false,
+ success_count_exception_list_items: 0, // there are no successful imports
+ }));
+
+ await render();
+
+ await ui.uploadFile([currentListId]);
+ await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
+
+ expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith({
+ title: "Artifacts weren't imported",
+ toastLifeTimeMs: 60_000,
+ });
+ expect(props.onSuccess).toHaveBeenCalled();
+ });
- it('should show an error toast if not only the current artifact type is included in the import file', async () => {
- await render();
+ it('should call `showErrorList` when the `View errors` button is pressed in the error toast', async () => {});
- await ui.uploadFile(['some-other-list-id', currentListId]);
- await userEvent.click(ui.getImportButton());
- await userEvent.click(ui.getConfirmModalConfirmButton());
+ it('should show an error toast and close the modal if another list is being imported', async () => {
+ await render();
- expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
- expect.objectContaining(
- new Error(artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError)
- ),
- { title: artifactListPageLabels.pageImportErrorToastTitle }
- );
- expect(ui.queryConfirmModal()).not.toBeInTheDocument();
- });
+ await ui.uploadFile(['some-other-list-id']);
+ await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
- it('should show an error toast if the import API fails', async () => {
- mockedTrustedAppApi.responseProvider.trustedAppImportList.mockImplementation(() => {
- throw new Error('Fail message from server');
+ expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
+ expect.objectContaining(
+ new Error(artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError)
+ ),
+ { title: artifactListPageLabels.pageImportErrorToastTitle }
+ );
+ expect(ui.queryConfirmModal()).not.toBeInTheDocument();
});
- await render();
+ it('should show an error toast if not only the current artifact type is included in the import file', async () => {
+ await render();
- await ui.uploadFile([currentListId]);
- await userEvent.click(ui.getImportButton());
- await userEvent.click(ui.getConfirmModalConfirmButton());
+ await ui.uploadFile(['some-other-list-id', currentListId]);
+ await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
- expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
- expect.objectContaining(new Error('Fail message from server')),
- { title: 'Artifact list import failed', toastMessage: 'Fail message from server' }
- );
- expect(ui.queryConfirmModal()).not.toBeInTheDocument();
+ expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
+ expect.objectContaining(
+ new Error(artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError)
+ ),
+ { title: artifactListPageLabels.pageImportErrorToastTitle }
+ );
+ expect(ui.queryConfirmModal()).not.toBeInTheDocument();
+ });
+
+ it('should show an error toast if the import API fails', async () => {
+ mockedTrustedAppApi.responseProvider.trustedAppImportList.mockImplementation(() => {
+ throw new Error('Fail message from server');
+ });
+
+ await render();
+
+ await ui.uploadFile([currentListId]);
+ await userEvent.click(ui.getImportButton());
+ await userEvent.click(ui.getConfirmModalConfirmButton());
+
+ expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
+ expect.objectContaining(new Error('Fail message from server')),
+ {
+ title: artifactListPageLabels.pageImportErrorToastTitle,
+ toastMessage: 'Fail message from server',
+ }
+ );
+ expect(ui.queryConfirmModal()).not.toBeInTheDocument();
+ });
});
});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
index 7c8104b419788..51fefe1a6f92c 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
@@ -84,22 +84,36 @@ export const ArtifactImportFlyout: React.FC = ({
setShowConfirmModal(false);
},
onSuccess: (response) => {
- toasts.addSuccess({
- title: labels.pageImportSuccessToastTitle,
- text: `${labels.getPageImportSuccessToastText?.(
- response.success_count_exception_list_items
- )}${
- response.errors.length
- ? ` ${response.errors.length} errors happened: ${response.errors
- .map(
- (item, index) =>
- `(${index + 1}) item (${item.item_id}): ${item.error.message}.`
- )
- .join(' -- ')}`
- : ''
- }`,
- toastLifeTimeMs: 60_000,
- });
+ if (response.success_exception_list_items === true) {
+ toasts.addSuccess({
+ title: labels.pageImportSuccessToastTitle,
+ text: labels.pageImportSuccessToastText,
+ toastLifeTimeMs: 60_000,
+ });
+ } else {
+ const itemErrors = response.errors.filter(
+ (error) =>
+ !(
+ error.error.status_code === 409 &&
+ error.error.message.match(/Found that list_id: "\w+" already exists/)
+ )
+ );
+
+ if (itemErrors.length > 0 && response.success_count_exception_list_items > 0) {
+ toasts.addWarning({
+ title: labels.pageImportCompletedWithErrorsToastTitle,
+ toastLifeTimeMs: 60_000,
+ });
+ }
+
+ if (itemErrors.length > 0 && response.success_count_exception_list_items === 0) {
+ toasts.addDanger({
+ title: labels.pageImportErrorToastTitle,
+ toastLifeTimeMs: 60_000,
+ });
+ }
+ }
+
setShowConfirmModal(false);
onSuccess();
},
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 bff004b3eca68..982077debcad5 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
@@ -50,19 +50,25 @@ export const artifactListPageLabels = Object.freeze({
pageImportSuccessToastTitle: i18n.translate(
'xpack.securitySolution.artifactListPage.importSuccessToastTitle',
{
- defaultMessage: 'Artifact list imported successfully',
+ defaultMessage: 'Artifacts imported',
+ }
+ ),
+ pageImportSuccessToastText: i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importSuccessToastText',
+ {
+ defaultMessage: 'All artifacts were imported successfully.',
+ }
+ ),
+ pageImportCompletedWithErrorsToastTitle: i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importCompletedWithErrorsToastTitle',
+ {
+ defaultMessage: 'Import completed with errors',
}
),
- getPageImportSuccessToastText: (successCount: number): string =>
- i18n.translate('xpack.securitySolution.artifactListPage.importSuccessToastText', {
- defaultMessage:
- '{successCount} {successCount, plural, one {artifact} other {artifacts}} imported.',
- values: { successCount },
- }),
pageImportErrorToastTitle: i18n.translate(
'xpack.securitySolution.artifactListPage.importErrorToastTitle',
{
- defaultMessage: 'Artifact list import failed',
+ defaultMessage: "Artifacts weren't imported",
}
),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
From 191862893f607876be9acd3f3a5c3634b3a277f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Wed, 1 Apr 2026 14:55:01 +0200
Subject: [PATCH 10/15] remove artifact specific success/error toast titles
---
.../components/artifact_import_flyout.tsx | 4 ++--
.../components/artifact_list_page/translations.ts | 2 --
.../management/pages/blocklist/view/blocklist.tsx | 12 ------------
.../pages/endpoint_exceptions/translations.tsx | 12 ------------
.../pages/event_filters/view/event_filters_list.tsx | 12 ------------
.../view/host_isolation_exceptions_list.tsx | 12 ------------
.../pages/trusted_apps/view/trusted_apps_list.tsx | 12 ------------
.../trusted_devices/view/trusted_devices_list.tsx | 12 ------------
8 files changed, 2 insertions(+), 76 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
index 51fefe1a6f92c..dad2d3dd3057c 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
@@ -23,7 +23,7 @@ import {
import React, { useCallback } from 'react';
import { parseListIdsFromImportedFile } from '../../../../common/utils/exception_list_items';
import { useToasts } from '../../../../common/lib/kibana';
-import type { ArtifactListPageLabels } from '../translations';
+import type { artifactListPageLabels } from '../translations';
import { useImportArtifactList } from '../../../hooks/artifacts/use_import_artifact_list';
import type { ExceptionsListApiClient } from '../../../services/exceptions_list/exceptions_list_api_client';
import { useTestIdGenerator } from '../../../hooks/use_test_id_generator';
@@ -33,7 +33,7 @@ export interface ArtifactImportFlyoutProps {
onCancel: () => void;
onSuccess: () => void;
apiClient: ExceptionsListApiClient;
- labels: ArtifactListPageLabels;
+ labels: typeof artifactListPageLabels;
'data-test-subj'?: string;
}
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 982077debcad5..a64ea99474527 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
@@ -183,8 +183,6 @@ export type ArtifactListPageRequiredLabels = Pick<
| 'pageExportButtonTitle'
| 'pageExportSuccessToastTitle'
| 'pageExportErrorToastTitle'
- | 'pageImportSuccessToastTitle'
- | 'pageImportErrorToastTitle'
| 'pageImportOnlyCurrentArtifactCanBeImportedError'
| 'getShowingCountLabel'
| 'cardActionEditLabel'
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 abfd6410b8656..11e8224efdba4 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
@@ -47,18 +47,6 @@ const BLOCKLIST_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Blocklist export failed',
}
),
- pageImportSuccessToastTitle: i18n.translate(
- 'xpack.securitySolution.blocklist.pageImportSuccessToastTitle',
- {
- defaultMessage: 'Blocklist imported successfully',
- }
- ),
- pageImportErrorToastTitle: i18n.translate(
- 'xpack.securitySolution.blocklist.pageImportErrorToastTitle',
- {
- defaultMessage: 'Blocklist import failed',
- }
- ),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.blocklist.pageImportOnlyCurrentArtifactCanBeImportedError',
{
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 0270b621bbd9e..91cabbfb91c7a 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
@@ -49,18 +49,6 @@ export const ENDPOINT_EXCEPTIONS_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Endpoint exception list export failed',
}
),
- pageImportSuccessToastTitle: i18n.translate(
- 'xpack.securitySolution.endpointExceptions.pageImportSuccessToastTitle',
- {
- defaultMessage: 'Endpoint exception list imported successfully',
- }
- ),
- pageImportErrorToastTitle: i18n.translate(
- 'xpack.securitySolution.endpointExceptions.pageImportErrorToastTitle',
- {
- defaultMessage: 'Endpoint exception list import failed',
- }
- ),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.endpointExceptions.pageImportOnlyCurrentArtifactCanBeImportedError',
{
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 91556af0d95b9..08a74be61d231 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
@@ -85,18 +85,6 @@ const EVENT_FILTERS_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Event filter list export failed',
}
),
- pageImportSuccessToastTitle: i18n.translate(
- 'xpack.securitySolution.eventFilters.pageImportSuccessToastTitle',
- {
- defaultMessage: 'Event filter list imported successfully',
- }
- ),
- pageImportErrorToastTitle: i18n.translate(
- 'xpack.securitySolution.eventFilters.pageImportErrorToastTitle',
- {
- defaultMessage: 'Event filter list import failed',
- }
- ),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.eventFilters.pageImportOnlyCurrentArtifactCanBeImportedError',
{
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 748b5934a72e4..41dce1a79c335 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
@@ -53,18 +53,6 @@ const HOST_ISOLATION_EXCEPTIONS_LABELS: ArtifactListPageLabels = Object.freeze({
defaultMessage: 'Host isolation exception list export failed',
}
),
- pageImportSuccessToastTitle: i18n.translate(
- 'xpack.securitySolution.hostIsolationExceptions.pageImportSuccessToastTitle',
- {
- defaultMessage: 'Host isolation exception list imported successfully',
- }
- ),
- pageImportErrorToastTitle: i18n.translate(
- 'xpack.securitySolution.hostIsolationExceptions.pageImportErrorToastTitle',
- {
- defaultMessage: 'Host isolation exception list import failed',
- }
- ),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.pageImportOnlyCurrentArtifactCanBeImportedError',
{
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 ce9e00a57c385..6b023437c5517 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
@@ -60,18 +60,6 @@ const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Trusted application list export failed',
}
),
- pageImportSuccessToastTitle: i18n.translate(
- 'xpack.securitySolution.trustedApps.pageImportSuccessToastTitle',
- {
- defaultMessage: 'Trusted application list imported successfully',
- }
- ),
- pageImportErrorToastTitle: i18n.translate(
- 'xpack.securitySolution.trustedApps.pageImportErrorToastTitle',
- {
- defaultMessage: 'Trusted application list import failed',
- }
- ),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.trustedApps.pageImportOnlyCurrentArtifactCanBeImportedError',
{
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 48201b1a11a4a..ecefa220073a6 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
@@ -66,18 +66,6 @@ const TRUSTED_DEVICES_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Trusted device list export failed',
}
),
- pageImportSuccessToastTitle: i18n.translate(
- 'xpack.securitySolution.trustedDevices.list.pageImportSuccessToastTitle',
- {
- defaultMessage: 'Trusted device list imported successfully',
- }
- ),
- pageImportErrorToastTitle: i18n.translate(
- 'xpack.securitySolution.trustedDevices.list.pageImportErrorToastTitle',
- {
- defaultMessage: 'Trusted device list import failed',
- }
- ),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.trustedDevices.list.pageImportOnlyCurrentArtifactCanBeImportedError',
{
From 20b0f1d83d01e6cb213a2fe06585825235bfb664 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Wed, 1 Apr 2026 15:00:58 +0200
Subject: [PATCH 11/15] remove 'list' from texts' wording
---
.../components/artifact_list_page/translations.ts | 9 ++++-----
.../management/pages/blocklist/view/blocklist.tsx | 12 ++++++------
.../pages/endpoint_exceptions/translations.tsx | 12 ++++++------
.../pages/event_filters/view/event_filters_list.tsx | 12 ++++++------
.../view/host_isolation_exceptions_list.tsx | 12 ++++++------
.../policy/view/artifacts/empty/translations.ts | 2 +-
.../policy/view/tabs/blocklists_translations.ts | 2 +-
.../view/tabs/endpoint_exceptions_translations.ts | 2 +-
.../policy/view/tabs/event_filters_translations.ts | 2 +-
.../tabs/host_isolation_exceptions_translations.ts | 2 +-
.../policy/view/tabs/trusted_apps_translations.ts | 2 +-
.../policy/view/tabs/trusted_devices_translations.ts | 2 +-
.../pages/trusted_apps/view/trusted_apps_list.tsx | 12 ++++++------
.../trusted_devices/view/trusted_devices_list.tsx | 12 ++++++------
14 files changed, 47 insertions(+), 48 deletions(-)
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 a64ea99474527..5b3f2004add54 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
@@ -26,7 +26,7 @@ export const artifactListPageLabels = Object.freeze({
pageImportButtonTitle: i18n.translate(
'xpack.securitySolution.artifactListPage.importButtonTitle',
{
- defaultMessage: 'Import artifact list',
+ defaultMessage: 'Import artifacts',
}
),
pageExportButtonTitle: i18n.translate(
@@ -80,14 +80,13 @@ export const artifactListPageLabels = Object.freeze({
importFlyoutDetails: i18n.translate(
'xpack.securitySolution.artifactListPage.importFlyoutDetails',
{
- defaultMessage:
- 'Attention: importing your artifacts will overwrite the existing list, which results in losing all existing artifacts that can be edited by the current user.',
+ defaultMessage: 'You can import artifacts to your artifact list.',
}
),
importFlyoutImportSubmitButtonLabel: i18n.translate(
'xpack.securitySolution.artifactListPage.importFlyoutImportSubmitButtonLabel',
{
- defaultMessage: 'Import list',
+ defaultMessage: 'Import artifacts',
}
),
@@ -112,7 +111,7 @@ export const artifactListPageLabels = Object.freeze({
),
emptyStateImportButtonLabel: i18n.translate(
'xpack.securitySolution.artifactListPage.emptyStateImportButtonLabel',
- { defaultMessage: 'Import list' }
+ { defaultMessage: 'Import artifacts' }
),
// ------------------------------
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 11e8224efdba4..b85dd416e280d 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
@@ -30,27 +30,27 @@ const BLOCKLIST_PAGE_LABELS: ArtifactListPageLabels = {
defaultMessage: 'Add blocklist entry',
}),
pageImportButtonTitle: i18n.translate('xpack.securitySolution.blocklist.pageImportButtonTitle', {
- defaultMessage: 'Import blocklist',
+ defaultMessage: 'Import blocklist entries',
}),
pageExportButtonTitle: i18n.translate('xpack.securitySolution.blocklist.pageExportButtonTitle', {
- defaultMessage: 'Export blocklist',
+ defaultMessage: 'Export blocklist entries',
}),
pageExportSuccessToastTitle: i18n.translate(
'xpack.securitySolution.blocklist.pageExportSuccessToastTitle',
{
- defaultMessage: 'Blocklist exported successfully',
+ defaultMessage: 'Blocklist entries exported successfully',
}
),
pageExportErrorToastTitle: i18n.translate(
'xpack.securitySolution.blocklist.pageExportErrorToastTitle',
{
- defaultMessage: 'Blocklist export failed',
+ defaultMessage: 'Blocklist entries export failed',
}
),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.blocklist.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only blocklist can be imported on this page.',
+ defaultMessage: 'Only blocklist entries can be imported on this page.',
}
),
getShowingCountLabel: (total) =>
@@ -127,7 +127,7 @@ const BLOCKLIST_PAGE_LABELS: ArtifactListPageLabels = {
),
emptyStateImportButtonLabel: i18n.translate(
'xpack.securitySolution.blocklist.emptyStateImportButtonLabel',
- { defaultMessage: 'Import blocklist' }
+ { defaultMessage: 'Import blocklist entries' }
),
searchPlaceholderInfo: i18n.translate('xpack.securitySolution.blocklist.searchPlaceholderInfo', {
defaultMessage: 'Search on the fields below: name, description, value',
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 91cabbfb91c7a..2a38296655765 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
@@ -28,31 +28,31 @@ export const ENDPOINT_EXCEPTIONS_PAGE_LABELS: ArtifactListPageLabels = {
pageImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpointExceptions.pageImportButtonTitle',
{
- defaultMessage: 'Import endpoint exception list',
+ defaultMessage: 'Import endpoint exceptions',
}
),
pageExportButtonTitle: i18n.translate(
'xpack.securitySolution.endpointExceptions.pageExportButtonTitle',
{
- defaultMessage: 'Export endpoint exception list',
+ defaultMessage: 'Export endpoint exceptions',
}
),
pageExportSuccessToastTitle: i18n.translate(
'xpack.securitySolution.endpointExceptions.exportSuccessToastTitle',
{
- defaultMessage: 'Endpoint exception list exported successfully',
+ defaultMessage: 'Endpoint exceptions exported successfully',
}
),
pageExportErrorToastTitle: i18n.translate(
'xpack.securitySolution.endpointExceptions.exportErrorToastTitle',
{
- defaultMessage: 'Endpoint exception list export failed',
+ defaultMessage: 'Endpoint exceptions export failed',
}
),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.endpointExceptions.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only endpoint exception list can be imported on this page.',
+ defaultMessage: 'Only endpoint exceptions can be imported on this page.',
}
),
getShowingCountLabel: (total) =>
@@ -135,7 +135,7 @@ export const ENDPOINT_EXCEPTIONS_PAGE_LABELS: ArtifactListPageLabels = {
),
emptyStateImportButtonLabel: i18n.translate(
'xpack.securitySolution.endpointExceptions.emptyStateImportButtonLabel',
- { defaultMessage: 'Import endpoint exception list' }
+ { defaultMessage: 'Import endpoint exceptions' }
),
searchPlaceholderInfo: i18n.translate(
'xpack.securitySolution.endpointExceptions.searchPlaceholderInfo',
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 08a74be61d231..3c21881187bff 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
@@ -64,31 +64,31 @@ const EVENT_FILTERS_PAGE_LABELS: ArtifactListPageLabels = {
pageImportButtonTitle: i18n.translate(
'xpack.securitySolution.eventFilters.pageImportButtonTitle',
{
- defaultMessage: 'Import event filter list',
+ defaultMessage: 'Import event filters',
}
),
pageExportButtonTitle: i18n.translate(
'xpack.securitySolution.eventFilters.pageExportButtonTitle',
{
- defaultMessage: 'Export event filter list',
+ defaultMessage: 'Export event filters',
}
),
pageExportSuccessToastTitle: i18n.translate(
'xpack.securitySolution.eventFilters.exportSuccessToastTitle',
{
- defaultMessage: 'Event filter list exported successfully',
+ defaultMessage: 'Event filters exported successfully',
}
),
pageExportErrorToastTitle: i18n.translate(
'xpack.securitySolution.eventFilters.exportErrorToastTitle',
{
- defaultMessage: 'Event filter list export failed',
+ defaultMessage: 'Event filters export failed',
}
),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.eventFilters.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only event filter list can be imported on this page.',
+ defaultMessage: 'Only event filters can be imported on this page.',
}
),
getShowingCountLabel: (total) =>
@@ -167,7 +167,7 @@ const EVENT_FILTERS_PAGE_LABELS: ArtifactListPageLabels = {
),
emptyStateImportButtonLabel: i18n.translate(
'xpack.securitySolution.eventFilters.emptyStateImportButtonLabel',
- { defaultMessage: 'Import event filter list' }
+ { defaultMessage: 'Import event filters' }
),
searchPlaceholderInfo: i18n.translate(
'xpack.securitySolution.eventFilters.searchPlaceholderInfo',
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 41dce1a79c335..55504da8bf5bf 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
@@ -32,31 +32,31 @@ const HOST_ISOLATION_EXCEPTIONS_LABELS: ArtifactListPageLabels = Object.freeze({
pageImportButtonTitle: i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.pageImportButtonTitle',
{
- defaultMessage: 'Import host isolation exception list',
+ defaultMessage: 'Import host isolation exceptions',
}
),
pageExportButtonTitle: i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.pageExportButtonTitle',
{
- defaultMessage: 'Export host isolation exception list',
+ defaultMessage: 'Export host isolation exceptions',
}
),
pageExportSuccessToastTitle: i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.exportSuccessToastTitle',
{
- defaultMessage: 'Host isolation exception list exported successfully',
+ defaultMessage: 'Host isolation exceptions exported successfully',
}
),
pageExportErrorToastTitle: i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.exportErrorToastTitle',
{
- defaultMessage: 'Host isolation exception list export failed',
+ defaultMessage: 'Host isolation exceptions export failed',
}
),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only host isolation exception list can be imported on this page.',
+ defaultMessage: 'Only host isolation exceptions can be imported on this page.',
}
),
getShowingCountLabel: (total) =>
@@ -139,7 +139,7 @@ const HOST_ISOLATION_EXCEPTIONS_LABELS: ArtifactListPageLabels = Object.freeze({
),
emptyStateImportButtonLabel: i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.emptyStateImportButtonLabel',
- { defaultMessage: 'Import host isolation exception list' }
+ { defaultMessage: 'Import host isolation exceptions' }
),
searchPlaceholderInfo: i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.searchPlaceholderInfo',
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/translations.ts
index 670984541f837..426146a0c5025 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/translations.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/translations.ts
@@ -55,6 +55,6 @@ export const POLICY_ARTIFACT_EMPTY_UNEXISTING_LABELS = Object.freeze({
),
emptyUnexistingImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpoint.policy.artifacts.empty.unexisting.importAction',
- { defaultMessage: 'Import artifact list' }
+ { defaultMessage: 'Import artifacts' }
),
});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/blocklists_translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/blocklists_translations.ts
index fda35e93120a9..85fc316f4c7d5 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/blocklists_translations.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/blocklists_translations.ts
@@ -132,7 +132,7 @@ export const POLICY_ARTIFACT_BLOCKLISTS_LABELS: Omit<
),
emptyUnexistingImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpoint.policy.blocklist.empty.unexisting.importAction',
- { defaultMessage: 'Import blocklist' }
+ { defaultMessage: 'Import blocklist entries' }
),
listTotalItemCountMessage: (totalItemsCount: number): string =>
i18n.translate('xpack.securitySolution.endpoint.policy.blocklists.list.totalItemCount', {
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/endpoint_exceptions_translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/endpoint_exceptions_translations.ts
index f96faf61aadf0..4587c840bc7f3 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/endpoint_exceptions_translations.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/endpoint_exceptions_translations.ts
@@ -138,7 +138,7 @@ export const POLICY_ARTIFACT_ENDPOINT_EXCEPTIONS_LABELS: Omit<
),
emptyUnexistingImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpoint.policy.endpointExceptions.empty.unexisting.importAction',
- { defaultMessage: 'Import endpoint exception list' }
+ { defaultMessage: 'Import endpoint exceptions' }
),
listTotalItemCountMessage: (totalItemsCount: number): string =>
i18n.translate(
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/event_filters_translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/event_filters_translations.ts
index be38abfda3a32..c331448b5ad9b 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/event_filters_translations.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/event_filters_translations.ts
@@ -130,7 +130,7 @@ export const POLICY_ARTIFACT_EVENT_FILTERS_LABELS: Omit<
),
emptyUnexistingImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpoint.policy.eventFilters.empty.unexisting.importAction',
- { defaultMessage: 'Import event filter list' }
+ { defaultMessage: 'Import event filters' }
),
listTotalItemCountMessage: (totalItemsCount: number): string =>
i18n.translate('xpack.securitySolution.endpoint.policy.eventFilters.list.totalItemCount', {
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/host_isolation_exceptions_translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/host_isolation_exceptions_translations.ts
index b59411991bd6d..438096a6ab23a 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/host_isolation_exceptions_translations.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/host_isolation_exceptions_translations.ts
@@ -139,7 +139,7 @@ export const POLICY_ARTIFACT_HOST_ISOLATION_EXCEPTIONS_LABELS: Omit<
),
emptyUnexistingImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpoint.policy.hostIsolationException.empty.unexisting.importAction',
- { defaultMessage: 'Import host isolation exception list' }
+ { defaultMessage: 'Import host isolation exceptions' }
),
listTotalItemCountMessage: (totalItemsCount: number): string =>
i18n.translate(
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_apps_translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_apps_translations.ts
index b20dbb42b2256..6fda907fed1e1 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_apps_translations.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_apps_translations.ts
@@ -130,7 +130,7 @@ export const POLICY_ARTIFACT_TRUSTED_APPS_LABELS: Omit<
),
emptyUnexistingImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpoint.policy.trustedApps.empty.unexisting.importAction',
- { defaultMessage: 'Import trusted application list' }
+ { defaultMessage: 'Import trusted applications' }
),
listTotalItemCountMessage: (totalItemsCount: number): string =>
i18n.translate('xpack.securitySolution.endpoint.policy.trustedApps.list.totalItemCount', {
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_devices_translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_devices_translations.ts
index 6916725de9741..5b0fe484e35dd 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_devices_translations.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_devices_translations.ts
@@ -133,7 +133,7 @@ export const POLICY_ARTIFACT_TRUSTED_DEVICES_LABELS: Omit<
),
emptyUnexistingImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpoint.policy.trustedDevices.empty.unexisting.importAction',
- { defaultMessage: 'Import trusted device list' }
+ { defaultMessage: 'Import trusted devices' }
),
listTotalItemCountMessage: (totalItemsCount: number): string =>
i18n.translate('xpack.securitySolution.endpoint.policy.trustedDevices.list.totalItemCount', {
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 6b023437c5517..62afa4759afbe 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
@@ -39,31 +39,31 @@ const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageLabels = {
pageImportButtonTitle: i18n.translate(
'xpack.securitySolution.trustedApps.pageImportButtonTitle',
{
- defaultMessage: 'Import trusted application list',
+ defaultMessage: 'Import trusted applications',
}
),
pageExportButtonTitle: i18n.translate(
'xpack.securitySolution.trustedApps.pageExportButtonTitle',
{
- defaultMessage: 'Export trusted application list',
+ defaultMessage: 'Export trusted applications',
}
),
pageExportSuccessToastTitle: i18n.translate(
'xpack.securitySolution.trustedApps.pageExportSuccessToastTitle',
{
- defaultMessage: 'Trusted application list exported successfully',
+ defaultMessage: 'Trusted applications exported successfully',
}
),
pageExportErrorToastTitle: i18n.translate(
'xpack.securitySolution.trustedApps.pageExportErrorToastTitle',
{
- defaultMessage: 'Trusted application list export failed',
+ defaultMessage: 'Trusted applications export failed',
}
),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.trustedApps.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only trusted application list can be imported on this page.',
+ defaultMessage: 'Only trusted applications can be imported on this page.',
}
),
getShowingCountLabel: (total) =>
@@ -143,7 +143,7 @@ const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageLabels = {
),
emptyStateImportButtonLabel: i18n.translate(
'xpack.securitySolution.trustedApps.emptyStateImportButtonLabel',
- { defaultMessage: 'Import trusted application list' }
+ { defaultMessage: 'Import trusted applications' }
),
searchPlaceholderInfo: i18n.translate(
'xpack.securitySolution.trustedApps.searchPlaceholderInfo',
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 ecefa220073a6..cd4574a8f9cb9 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
@@ -45,31 +45,31 @@ const TRUSTED_DEVICES_PAGE_LABELS: ArtifactListPageLabels = {
pageImportButtonTitle: i18n.translate(
'xpack.securitySolution.trustedDevices.list.pageImportButtonTitle',
{
- defaultMessage: 'Import trusted device list',
+ defaultMessage: 'Import trusted devices',
}
),
pageExportButtonTitle: i18n.translate(
'xpack.securitySolution.trustedDevices.list.pageExportButtonTitle',
{
- defaultMessage: 'Export trusted device list',
+ defaultMessage: 'Export trusted devices',
}
),
pageExportSuccessToastTitle: i18n.translate(
'xpack.securitySolution.trustedDevices.list.pageExportSuccessToastTitle',
{
- defaultMessage: 'Trusted device list exported successfully',
+ defaultMessage: 'Trusted devices exported successfully',
}
),
pageExportErrorToastTitle: i18n.translate(
'xpack.securitySolution.trustedDevices.list.pageExportErrorToastTitle',
{
- defaultMessage: 'Trusted device list export failed',
+ defaultMessage: 'Trusted devices export failed',
}
),
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.trustedDevices.list.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only trusted device list can be imported on this page.',
+ defaultMessage: 'Only trusted devices can be imported on this page.',
}
),
getShowingCountLabel: (total) =>
@@ -159,7 +159,7 @@ const TRUSTED_DEVICES_PAGE_LABELS: ArtifactListPageLabels = {
),
emptyStateImportButtonLabel: i18n.translate(
'xpack.securitySolution.trustedDevices.list.emptyStateImportButtonLabel',
- { defaultMessage: 'Import trusted device list' }
+ { defaultMessage: 'Import trusted devices' }
),
searchPlaceholderInfo: i18n.translate(
'xpack.securitySolution.trustedDevices.list.searchPlaceholderInfo',
From 90047a587ed537e284d0d1f31b44c19c8d012efa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Wed, 1 Apr 2026 18:39:00 +0200
Subject: [PATCH 12/15] show import error results on a new modal
---
.../artifact_list_page/artifact_list_page.tsx | 19 ++-
.../artifact_import_confirm_modal.tsx | 1 -
.../artifact_import_errors_modal.test.tsx | 111 ++++++++++++++++
.../artifact_import_errors_modal.tsx | 121 ++++++++++++++++++
.../artifact_import_flyout.test.tsx | 35 +++--
.../components/artifact_import_flyout.tsx | 54 +++++++-
.../artifact_list_page.test.tsx | 1 +
.../artifact_list_page/translations.ts | 25 ++++
8 files changed, 342 insertions(+), 25 deletions(-)
create mode 100644 x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_errors_modal.test.tsx
create mode 100644 x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_errors_modal.tsx
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 2a915f5a7b5ff..ec3825c8ead4a 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
@@ -7,7 +7,10 @@
import React, { memo, useCallback, useMemo, useState, useEffect } from 'react';
-import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
+import type {
+ BulkErrorSchema,
+ ExceptionListItemSchema,
+} from '@kbn/securitysolution-io-ts-list-types';
import { EuiButton, EuiFlexGroup, EuiSpacer, EuiText } from '@elastic/eui';
import type { EuiFlyoutSize } from '@elastic/eui/src/components/flyout/flyout';
import { useLocation } from 'react-router-dom';
@@ -52,6 +55,7 @@ import { BackToExternalAppButton } from '../back_to_external_app_button';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import { ArtifactImportFlyout } from './components/artifact_import_flyout';
import { useIsImportFlyoutOpened } from './hooks/use_is_import_flyout_opened';
+import { ArtifactImportErrorsModal } from './components/artifact_import_errors_modal';
type ArtifactEntryCardType = typeof ArtifactEntryCard;
@@ -118,6 +122,8 @@ export const ArtifactListPage = memo(
useIsImportFlyoutOpened(allowCardCreateAction) &&
areEndpointExceptionsMovedUnderManagementFFEnabled;
+ const [importErrors, setImportErrors] = useState(undefined);
+
const setUrlParams = useSetUrlParams();
const {
urlParams: { filter, includedPolicies },
@@ -298,6 +304,12 @@ export const ArtifactListPage = memo(
refetchListData();
}, [closeImportFlyout, refetchListData]);
+ const handleImportFlyoutOnShowErrors = useCallback((errors: BulkErrorSchema[]) => {
+ setImportErrors(errors);
+ }, []);
+
+ const handleCloseImportErrorsModal = useCallback(() => setImportErrors(undefined), []);
+
const description = useMemo(() => {
const subtitleText = labels.pageAboutInfo ? (
{labels.pageAboutInfo}
@@ -409,11 +421,16 @@ export const ArtifactListPage = memo(
)}
+ {importErrors && (
+
+ )}
+
{selectedItemForDelete && (
- {' '}
{i18n.translate('xpack.securitySolution.artifactListPage.importConfirmModal.title', {
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_errors_modal.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_errors_modal.test.tsx
new file mode 100644
index 0000000000000..2de2282c65aee
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_errors_modal.test.tsx
@@ -0,0 +1,111 @@
+/*
+ * 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 React from 'react';
+import type { ArtifactListPageProps } from '../artifact_list_page';
+import userEvent from '@testing-library/user-event';
+import {
+ createAppRootMockRenderer,
+ type AppContextTestRender,
+} from '../../../../common/mock/endpoint';
+import type { ArtifactImportErrorsModalProps } from './artifact_import_errors_modal';
+import { ArtifactImportErrorsModal } from './artifact_import_errors_modal';
+
+describe('When the flyout is opened in the ArtifactListPage component', () => {
+ let render: (
+ props?: Partial
+ ) => Promise>;
+ let renderResult: ReturnType;
+ let props: ArtifactImportErrorsModalProps;
+
+ beforeEach(() => {
+ const mockedContext = createAppRootMockRenderer();
+
+ props = {
+ errors: [],
+ onClose: jest.fn(),
+ };
+
+ render = async () => {
+ renderResult = mockedContext.render(
+
+ );
+ return renderResult;
+ };
+ });
+
+ it('should display `Close` button', async () => {
+ await render();
+
+ expect(renderResult.getByTestId('testModal')).toBeInTheDocument();
+ });
+
+ it('should call `onClose` when the `Close` button is clicked', async () => {
+ await render();
+
+ await userEvent.click(renderResult.getByTestId('testModal-closeButton'));
+
+ expect(props.onClose).toHaveBeenCalled();
+ });
+
+ it('should display the list of errors with item ids and messages', async () => {
+ props.errors = [
+ {
+ item_id: 'item1',
+ error: { message: 'Error message 1', status_code: 403 },
+ },
+ {
+ item_id: 'item2',
+ error: { message: 'Error message 2', status_code: 403 },
+ },
+ ];
+
+ await render();
+
+ expect(renderResult.getByText('Import errors')).toBeInTheDocument();
+ expect(renderResult.getByText('item (item1):')).toBeInTheDocument();
+ expect(renderResult.getByText('Error message 1')).toBeInTheDocument();
+ expect(renderResult.getByText('item (item2):')).toBeInTheDocument();
+ expect(renderResult.getByText('Error message 2')).toBeInTheDocument();
+ });
+
+ it('should handle the case when item_id is undefined', async () => {
+ props.errors = [
+ {
+ error: { message: 'Error message without item_id', status_code: 403 },
+ },
+ ];
+
+ await render();
+
+ expect(renderResult.getByText('Import errors')).toBeInTheDocument();
+ expect(renderResult.getByText('item (undefined):')).toBeInTheDocument();
+ expect(renderResult.getByText('Error message without item_id')).toBeInTheDocument();
+ expect(renderResult.getByText(/Error message /)).toBeInTheDocument();
+ });
+
+ it('should remove "EndpointArtifactError: " prefix from error messages', async () => {
+ props.errors = [
+ {
+ item_id: 'item1',
+ error: {
+ message: 'EndpointArtifactError: This is an endpoint artifact error message',
+ status_code: 403,
+ },
+ },
+ ];
+
+ await render();
+
+ expect(renderResult.getByText('Import errors')).toBeInTheDocument();
+ expect(renderResult.getByText('item (item1):')).toBeInTheDocument();
+ expect(
+ renderResult.getByText(/^This is an endpoint artifact error message/)
+ ).toBeInTheDocument();
+ expect(renderResult.queryByText(/EndpointArtifactError/)).not.toBeInTheDocument();
+ });
+});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_errors_modal.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_errors_modal.tsx
new file mode 100644
index 0000000000000..d95bd5a746742
--- /dev/null
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_errors_modal.tsx
@@ -0,0 +1,121 @@
+/*
+ * 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 React, { useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+
+import {
+ EuiButton,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiSpacer,
+ EuiText,
+ useGeneratedHtmlId,
+} from '@elastic/eui';
+import type { BulkErrorSchema } from '@kbn/securitysolution-io-ts-list-types';
+import { useTestIdGenerator } from '../../../hooks/use_test_id_generator';
+
+export interface ArtifactImportErrorsModalProps {
+ errors: BulkErrorSchema[];
+ onClose: () => void;
+ 'data-test-subj'?: string;
+}
+
+export const ArtifactImportErrorsModal: React.FC = ({
+ errors,
+ onClose,
+ 'data-test-subj': dataTestSubj = 'artifactImportErrorsModal',
+}) => {
+ const getTestId = useTestIdGenerator(dataTestSubj);
+ const modalTitleId = useGeneratedHtmlId();
+
+ const errorsToDisplay: Array<{ itemId: string; message: string }> = useMemo(
+ () =>
+ errors.map((error) => ({
+ itemId: error.item_id ?? 'undefined',
+ message: error.error.message.replace('EndpointArtifactError: ', ''),
+ })),
+ [errors]
+ );
+
+ return (
+
+
+
+ {i18n.translate('xpack.securitySolution.artifactListPage.importErrorsModal.title', {
+ defaultMessage: 'Import errors',
+ })}
+
+
+
+
+
+ {i18n.translate('xpack.securitySolution.artifactListPage.importErrorsModal.info', {
+ defaultMessage:
+ "Some items couldn't be imported. Review the errors below for details.",
+ })}
+
+
+
+
+
+ ({
+ maxHeight: '50vh',
+ overflowY: 'auto',
+ border: `1px solid ${euiTheme.euiTheme.colors.borderBasePlain}`,
+ })}
+ direction="column"
+ >
+
+
+
+ {errorsToDisplay.map((error, index) => (
+ - ({
+ '&::marker': { fontWeight: 'bold' },
+ margin: euiTheme.euiTheme.size.m,
+ })}
+ key={index}
+ >
+
+ {i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importErrorsModal.item',
+ {
+ defaultMessage: 'item ({itemId}):',
+ values: { itemId: error.itemId },
+ }
+ )}
+ {' '}
+ {error.message}
+
+ ))}
+
+
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importErrorsModal.closeButtonTitle',
+ { defaultMessage: 'Close' }
+ )}
+
+
+
+ );
+};
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
index 0f3650f144826..3d41673b071e4 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.test.tsx
@@ -47,6 +47,7 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
apiClient,
onCancel: jest.fn(),
onSuccess: jest.fn(),
+ onShowErrors: jest.fn(),
};
render = async () => {
@@ -136,8 +137,10 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
await userEvent.click(ui.getImportButton());
await userEvent.click(ui.getConfirmModalConfirmButton());
- expect(ui.getImportButton()).toBeDisabled();
- expect(ui.getConfirmModalConfirmButton()).toBeDisabled();
+ await waitFor(() => {
+ expect(ui.getImportButton()).toBeDisabled();
+ expect(ui.getConfirmModalConfirmButton()).toBeDisabled();
+ });
});
describe('when handling server response', () => {
@@ -230,12 +233,11 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledWith({
title: 'Import completed with errors',
toastLifeTimeMs: 60_000,
+ text: expect.any(Function),
});
expect(props.onSuccess).toHaveBeenCalled();
});
- it('should call `showErrorList` when the `View errors` button is pressed in the warning toast', async () => {});
-
it('should show a danger toast when none of the items were imported', async () => {
mockedTrustedAppApi.responseProvider.trustedAppImportList.mockImplementation(() => ({
errors: [LIST_CONFLICT_ERROR, ITEM_CONFLICT_ERROR, ITEM_ENDPOINT_ARTIFACT_ERROR],
@@ -256,25 +258,22 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith({
title: "Artifacts weren't imported",
toastLifeTimeMs: 60_000,
+ text: expect.any(Function),
});
expect(props.onSuccess).toHaveBeenCalled();
});
- it('should call `showErrorList` when the `View errors` button is pressed in the error toast', async () => {});
-
- it('should show an error toast and close the modal if another list is being imported', async () => {
+ it('should show an danger toast and close the modal if another list is being imported', async () => {
await render();
await ui.uploadFile(['some-other-list-id']);
await userEvent.click(ui.getImportButton());
await userEvent.click(ui.getConfirmModalConfirmButton());
- expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
- expect.objectContaining(
- new Error(artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError)
- ),
- { title: artifactListPageLabels.pageImportErrorToastTitle }
- );
+ expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith({
+ title: artifactListPageLabels.pageImportErrorToastTitle,
+ text: artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError,
+ });
expect(ui.queryConfirmModal()).not.toBeInTheDocument();
});
@@ -285,12 +284,10 @@ describe('When the flyout is opened in the ArtifactListPage component', () => {
await userEvent.click(ui.getImportButton());
await userEvent.click(ui.getConfirmModalConfirmButton());
- expect(coreStart.notifications.toasts.addError).toHaveBeenCalledWith(
- expect.objectContaining(
- new Error(artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError)
- ),
- { title: artifactListPageLabels.pageImportErrorToastTitle }
- );
+ expect(coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith({
+ title: artifactListPageLabels.pageImportErrorToastTitle,
+ text: artifactListPageLabels.pageImportOnlyCurrentArtifactCanBeImportedError,
+ });
expect(ui.queryConfirmModal()).not.toBeInTheDocument();
});
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
index dad2d3dd3057c..9b0803324fbc9 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
@@ -21,8 +21,10 @@ import {
useGeneratedHtmlId,
} from '@elastic/eui';
import React, { useCallback } from 'react';
+import { toMountPoint } from '@kbn/react-kibana-mount';
+import type { BulkErrorSchema } from '@kbn/securitysolution-io-ts-list-types';
import { parseListIdsFromImportedFile } from '../../../../common/utils/exception_list_items';
-import { useToasts } from '../../../../common/lib/kibana';
+import { useKibana, useToasts } from '../../../../common/lib/kibana';
import type { artifactListPageLabels } from '../translations';
import { useImportArtifactList } from '../../../hooks/artifacts/use_import_artifact_list';
import type { ExceptionsListApiClient } from '../../../services/exceptions_list/exceptions_list_api_client';
@@ -32,19 +34,41 @@ import { ArtifactImportConfirmModal } from './artifact_import_confirm_modal';
export interface ArtifactImportFlyoutProps {
onCancel: () => void;
onSuccess: () => void;
+ onShowErrors: (errors: BulkErrorSchema[]) => void;
apiClient: ExceptionsListApiClient;
labels: typeof artifactListPageLabels;
'data-test-subj'?: string;
}
+const ArtifactImportErrorToast: React.FC<{
+ text: string;
+ buttonLabel: string;
+ errors: BulkErrorSchema[];
+ onShowErrors: (errors: BulkErrorSchema[]) => void;
+}> = ({ text, buttonLabel, errors, onShowErrors }) => {
+ return (
+ <>
+ {text}
+
+
+ onShowErrors(errors)}>
+ {buttonLabel}
+
+
+ >
+ );
+};
+
export const ArtifactImportFlyout: React.FC = ({
onCancel,
onSuccess,
+ onShowErrors,
apiClient,
labels,
'data-test-subj': dataTestSubj = 'artifactImportFlyout',
}) => {
const toasts = useToasts();
+ const services = useKibana().services;
const getTestId = useTestIdGenerator(dataTestSubj);
const [file, setFile] = React.useState(null);
@@ -65,8 +89,9 @@ export const ArtifactImportFlyout: React.FC = ({
const listIds = await parseListIdsFromImportedFile(file);
if (listIds.size > 1 || !listIds.has(apiClient.listId)) {
- toasts.addError(new Error(labels.pageImportOnlyCurrentArtifactCanBeImportedError), {
+ toasts.addDanger({
title: labels.pageImportErrorToastTitle,
+ text: labels.pageImportOnlyCurrentArtifactCanBeImportedError,
});
setShowConfirmModal(false);
@@ -91,7 +116,7 @@ export const ArtifactImportFlyout: React.FC = ({
toastLifeTimeMs: 60_000,
});
} else {
- const itemErrors = response.errors.filter(
+ const itemErrors: BulkErrorSchema[] = response.errors.filter(
(error) =>
!(
error.error.status_code === 409 &&
@@ -103,6 +128,18 @@ export const ArtifactImportFlyout: React.FC = ({
toasts.addWarning({
title: labels.pageImportCompletedWithErrorsToastTitle,
toastLifeTimeMs: 60_000,
+ text: toMountPoint(
+ ,
+ services
+ ),
});
}
@@ -110,6 +147,15 @@ export const ArtifactImportFlyout: React.FC = ({
toasts.addDanger({
title: labels.pageImportErrorToastTitle,
toastLifeTimeMs: 60_000,
+ text: toMountPoint(
+ ,
+ services
+ ),
});
}
}
@@ -120,7 +166,7 @@ export const ArtifactImportFlyout: React.FC = ({
}
);
}
- }, [apiClient.listId, file, labels, mutate, onSuccess, toasts]);
+ }, [apiClient.listId, file, labels, mutate, onShowErrors, onSuccess, services, toasts]);
const handleOnFileChange: EuiFilePickerProps['onChange'] = useCallback(
(files: FileList | null) => {
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx
index 14d61bd9fabdc..0a3575481c6c9 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/integration_tests/artifact_list_page.test.tsx
@@ -236,6 +236,7 @@ describe('When using the ArtifactListPage component', () => {
const currentApiCallCount = mockedApi.responseProvider.trustedAppsList.mock.calls.length;
await userEvent.click(importFlyoutUi.getImportButton());
+ await userEvent.click(importFlyoutUi.getConfirmModalConfirmButton());
await waitFor(() => {
expect(mockedApi.responseProvider.trustedAppsList).toHaveBeenCalledTimes(
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 5b3f2004add54..fdb95667947fe 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
@@ -65,6 +65,31 @@ export const artifactListPageLabels = Object.freeze({
defaultMessage: 'Import completed with errors',
}
),
+ getPageImportCompletedWithErrorsToastText: (
+ importedCount: number,
+ failedCount: number
+ ): string => {
+ return i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importCompletedWithErrorsToastText',
+ {
+ defaultMessage:
+ '{importedCount} imported, {failedCount} failed. Review the errors for details.',
+ values: { importedCount, failedCount },
+ }
+ );
+ },
+ pageImportAllItemsFailedToastText: i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importAllItemsFailedToastText',
+ {
+ defaultMessage: "The artifacts couldn't be imported. Review the errors and try again.",
+ }
+ ),
+ pageImportViewErrorsButton: i18n.translate(
+ 'xpack.securitySolution.artifactListPage.importViewErrorsButton',
+ {
+ defaultMessage: 'View errors',
+ }
+ ),
pageImportErrorToastTitle: i18n.translate(
'xpack.securitySolution.artifactListPage.importErrorToastTitle',
{
From e4be5cefa6189050f54fc8ddfdeaa48a2e53e53f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Thu, 2 Apr 2026 10:56:38 +0200
Subject: [PATCH 13/15] adapt API tests to the new error messages
---
.../artifact_import.ts | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/artifact_import.ts b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/artifact_import.ts
index bf36e7c5b2593..b3168b026fde8 100644
--- a/x-pack/solutions/security/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/artifact_import.ts
+++ b/x-pack/solutions/security/test/security_solution_api_integration/test_suites/edr_workflows/artifacts/trial_license_complete_tier/artifact_import.ts
@@ -327,7 +327,7 @@ export default function artifactImportAPIIntegrationTests({ getService }: FtrPro
{
error: {
message:
- 'EndpointArtifactError: Endpoint authorization failure. Importing artifacts to a different space requires global artifact management privilege',
+ "EndpointArtifactError: Endpoint authorization failure. This artifact can't be imported because you don't have permission to manage artifacts in other spaces. Contact your administrator for access.",
status_code: 403,
},
item_id: 'wrong-item',
@@ -373,7 +373,7 @@ export default function artifactImportAPIIntegrationTests({ getService }: FtrPro
{
error: {
message:
- 'EndpointArtifactError: Endpoint authorization failure. Management of global artifacts requires additional privilege (global artifact management)',
+ "EndpointArtifactError: Endpoint authorization failure. This artifact can't be imported because you don't have permission to manage global artifacts. Contact your administrator for access.",
status_code: 403,
},
item_id: 'wrong-item',
@@ -498,7 +498,7 @@ export default function artifactImportAPIIntegrationTests({ getService }: FtrPro
{
error: {
message:
- 'EndpointArtifactError: Endpoint authorization failure. Importing artifacts that are not visible in the current space is not allowed',
+ "EndpointArtifactError: Endpoint authorization failure. This artifact can't be imported because it isn't visible in the current space. Try importing it from a matching space or a space with access to the related policy.",
status_code: 403,
},
item_id: 'not-visible-in-current-space-because-unassigned',
@@ -507,7 +507,7 @@ export default function artifactImportAPIIntegrationTests({ getService }: FtrPro
{
error: {
message:
- 'EndpointArtifactError: Endpoint authorization failure. Importing artifacts that are not visible in the current space is not allowed',
+ "EndpointArtifactError: Endpoint authorization failure. This artifact can't be imported because it isn't visible in the current space. Try importing it from a matching space or a space with access to the related policy.",
status_code: 403,
},
item_id: 'not-visible-in-current-space-because-assigned-only-there',
@@ -555,7 +555,7 @@ export default function artifactImportAPIIntegrationTests({ getService }: FtrPro
{
error: {
message:
- 'EndpointArtifactError: Endpoint authorization failure. Importing artifacts to a different space requires global artifact management privilege',
+ "EndpointArtifactError: Endpoint authorization failure. This artifact can't be imported because you don't have permission to manage artifacts in other spaces. Contact your administrator for access.",
status_code: 403,
},
item_id: 'global-artifact-with-invalid-space-id',
@@ -564,7 +564,7 @@ export default function artifactImportAPIIntegrationTests({ getService }: FtrPro
{
error: {
message:
- 'EndpointArtifactError: Endpoint authorization failure. Importing artifacts to a different space requires global artifact management privilege',
+ "EndpointArtifactError: Endpoint authorization failure. This artifact can't be imported because you don't have permission to manage artifacts in other spaces. Contact your administrator for access.",
status_code: 403,
},
item_id: 'per-policy-artifact-with-invalid-space-id',
@@ -611,7 +611,7 @@ export default function artifactImportAPIIntegrationTests({ getService }: FtrPro
{
error: {
message:
- 'EndpointArtifactError: Importing artifacts with invalid owner space IDs is not allowed. The following space ID is invalid or unaccessible by current user: i-dont-exist-1',
+ "EndpointArtifactError: This artifact can't be imported because it belongs to a space you don't have access to. Update the artifact in its original space and try again.",
status_code: 403,
},
item_id: 'global-artifact-with-invalid-space-id',
@@ -620,7 +620,7 @@ export default function artifactImportAPIIntegrationTests({ getService }: FtrPro
{
error: {
message:
- 'EndpointArtifactError: Importing artifacts with invalid owner space IDs is not allowed. The following space IDs are invalid or unaccessible by current user: i-dont-exist-2, i-dont-exist-3',
+ "EndpointArtifactError: This artifact can't be imported because it belongs to a space you don't have access to. Update the artifact in its original space and try again.",
status_code: 403,
},
item_id: 'per-policy-artifact-with-invalid-space-id',
@@ -1350,7 +1350,7 @@ export default function artifactImportAPIIntegrationTests({ getService }: FtrPro
{
error: {
message:
- 'EndpointArtifactError: Endpoint authorization failure. Management of global artifacts requires additional privilege (global artifact management)',
+ "EndpointArtifactError: Endpoint authorization failure. This artifact can't be imported because you don't have permission to manage global artifacts. Contact your administrator for access.",
status_code: 403,
},
item_id: 'imported-artifact',
From 365d6b9d4106577fae86e5393eae402ed5ecbbff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Thu, 2 Apr 2026 15:41:46 +0200
Subject: [PATCH 14/15] update texts
---
.../public/exceptions/translations/shared_list.ts | 2 +-
.../components/artifact_import_confirm_modal.tsx | 2 +-
.../components/artifact_list_page/translations.ts | 4 ++--
.../public/management/pages/blocklist/view/blocklist.tsx | 2 +-
.../management/pages/endpoint_exceptions/translations.tsx | 8 ++++----
.../pages/event_filters/view/event_filters_list.tsx | 2 +-
.../view/host_isolation_exceptions_list.tsx | 2 +-
.../policy/view/tabs/endpoint_exceptions_translations.ts | 2 +-
.../pages/trusted_apps/view/trusted_apps_list.tsx | 2 +-
.../pages/trusted_devices/view/trusted_devices_list.tsx | 2 +-
10 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/exceptions/translations/shared_list.ts b/x-pack/solutions/security/plugins/security_solution/public/exceptions/translations/shared_list.ts
index 0d66f0f656910..550e90a640137 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/exceptions/translations/shared_list.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/exceptions/translations/shared_list.ts
@@ -217,7 +217,7 @@ export const IMPORT_ENDPOINT_ARTIFACTS_ERROR_TEXT = i18n.translate(
'xpack.securitySolution.exceptionsTable.importEndpointArtifactsErrorText',
{
defaultMessage:
- 'On this page only shared exception lists can be imported, but at least one file contains Endpoint artifacts. Endpoint artifacts can be imported on their respective pages.',
+ 'You can only import shared exception lists here, but at least one of the imported files contains endpoint artifacts. Import endpoint artifacts from their dedicated pages instead.',
}
);
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_confirm_modal.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_confirm_modal.tsx
index 323bc556a610c..d21c7a292ca9d 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_confirm_modal.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_confirm_modal.tsx
@@ -49,7 +49,7 @@ export const ArtifactImportConfirmModal: React.FC
{i18n.translate('xpack.securitySolution.artifactListPage.importConfirmModal.info', {
defaultMessage:
- 'This will add new artifacts to your list. If an imported artifact already exists, the current version will be kept and the duplicate will be skipped.',
+ "This will add new artifacts to your list. If an artifact you're importing already exists, the existing version will be kept, and the import of that artifact will be skipped.",
})}
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 fdb95667947fe..0c7ba43bd9c45 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
@@ -99,13 +99,13 @@ export const artifactListPageLabels = Object.freeze({
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.artifactListPage.importOnlyCurrentArtifactCanBeImportedToastMessage',
{
- defaultMessage: 'Only the current artifact type can be imported on this page.',
+ defaultMessage: 'You can only import the current artifact type here.',
}
),
importFlyoutDetails: i18n.translate(
'xpack.securitySolution.artifactListPage.importFlyoutDetails',
{
- defaultMessage: 'You can import artifacts to your artifact list.',
+ defaultMessage: 'Import artifacts to your artifact list.',
}
),
importFlyoutImportSubmitButtonLabel: i18n.translate(
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 b85dd416e280d..39667dbcb58e9 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
@@ -50,7 +50,7 @@ const BLOCKLIST_PAGE_LABELS: ArtifactListPageLabels = {
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.blocklist.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only blocklist entries can be imported on this page.',
+ defaultMessage: 'You can only import blocklist entries here.',
}
),
getShowingCountLabel: (total) =>
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 2a38296655765..fd43d985d5a0b 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
@@ -28,13 +28,13 @@ export const ENDPOINT_EXCEPTIONS_PAGE_LABELS: ArtifactListPageLabels = {
pageImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpointExceptions.pageImportButtonTitle',
{
- defaultMessage: 'Import endpoint exceptions',
+ defaultMessage: 'Import Endpoint exceptions',
}
),
pageExportButtonTitle: i18n.translate(
'xpack.securitySolution.endpointExceptions.pageExportButtonTitle',
{
- defaultMessage: 'Export endpoint exceptions',
+ defaultMessage: 'Export Endpoint exceptions',
}
),
pageExportSuccessToastTitle: i18n.translate(
@@ -52,7 +52,7 @@ export const ENDPOINT_EXCEPTIONS_PAGE_LABELS: ArtifactListPageLabels = {
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.endpointExceptions.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only endpoint exceptions can be imported on this page.',
+ defaultMessage: 'You can only import Endpoint exceptions here.',
}
),
getShowingCountLabel: (total) =>
@@ -135,7 +135,7 @@ export const ENDPOINT_EXCEPTIONS_PAGE_LABELS: ArtifactListPageLabels = {
),
emptyStateImportButtonLabel: i18n.translate(
'xpack.securitySolution.endpointExceptions.emptyStateImportButtonLabel',
- { defaultMessage: 'Import endpoint exceptions' }
+ { defaultMessage: 'Import Endpoint exceptions' }
),
searchPlaceholderInfo: i18n.translate(
'xpack.securitySolution.endpointExceptions.searchPlaceholderInfo',
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 3c21881187bff..18c6e0639e601 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
@@ -88,7 +88,7 @@ const EVENT_FILTERS_PAGE_LABELS: ArtifactListPageLabels = {
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.eventFilters.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only event filters can be imported on this page.',
+ defaultMessage: 'You can only import event filters here.',
}
),
getShowingCountLabel: (total) =>
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 55504da8bf5bf..2575c733735f9 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
@@ -56,7 +56,7 @@ const HOST_ISOLATION_EXCEPTIONS_LABELS: ArtifactListPageLabels = Object.freeze({
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.hostIsolationExceptions.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only host isolation exceptions can be imported on this page.',
+ defaultMessage: 'You can only import host isolation exceptions here.',
}
),
getShowingCountLabel: (total) =>
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/endpoint_exceptions_translations.ts b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/endpoint_exceptions_translations.ts
index 4587c840bc7f3..eacae23120474 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/endpoint_exceptions_translations.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/pages/policy/view/tabs/endpoint_exceptions_translations.ts
@@ -138,7 +138,7 @@ export const POLICY_ARTIFACT_ENDPOINT_EXCEPTIONS_LABELS: Omit<
),
emptyUnexistingImportButtonTitle: i18n.translate(
'xpack.securitySolution.endpoint.policy.endpointExceptions.empty.unexisting.importAction',
- { defaultMessage: 'Import endpoint exceptions' }
+ { defaultMessage: 'Import Endpoint exceptions' }
),
listTotalItemCountMessage: (totalItemsCount: number): string =>
i18n.translate(
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 62afa4759afbe..9f03a146c4490 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
@@ -63,7 +63,7 @@ const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageLabels = {
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.trustedApps.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only trusted applications can be imported on this page.',
+ defaultMessage: 'You can only import trusted applications here.',
}
),
getShowingCountLabel: (total) =>
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 cd4574a8f9cb9..dfb087159d0af 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
@@ -69,7 +69,7 @@ const TRUSTED_DEVICES_PAGE_LABELS: ArtifactListPageLabels = {
pageImportOnlyCurrentArtifactCanBeImportedError: i18n.translate(
'xpack.securitySolution.trustedDevices.list.pageImportOnlyCurrentArtifactCanBeImportedError',
{
- defaultMessage: 'Only trusted devices can be imported on this page.',
+ defaultMessage: 'You can only import trusted devices here.',
}
),
getShowingCountLabel: (total) =>
From 2d116edab2ed125782d2855df4870d05b192c86c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gerg=C5=91=20=C3=81brah=C3=A1m?=
Date: Thu, 2 Apr 2026 16:51:30 +0200
Subject: [PATCH 15/15] memoize handler functions
---
.../import_exceptions_list_flyout/index.tsx | 25 ++++++++++---------
.../components/artifact_import_flyout.tsx | 6 ++++-
2 files changed, 18 insertions(+), 13 deletions(-)
diff --git a/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx
index ab66e3d30d9b8..93806adad554a 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/exceptions/components/import_exceptions_list_flyout/index.tsx
@@ -195,6 +195,16 @@ export const ImportExceptionListFlyout = React.memo(
setFiles(inputFiles ?? null);
}, []);
+ const handleNewListCheckboxChange = useCallback(() => {
+ setAsNewList((prev) => !prev);
+ setOverwrite(false);
+ }, []);
+
+ const handleOverwriteCheckboxChange = useCallback((): void => {
+ setOverwrite((prev) => !prev);
+ setAsNewList(false);
+ }, []);
+
const importExceptionListFlyoutTitleId = useGeneratedHtmlId({
prefix: 'importExceptionListFlyoutTitle',
});
@@ -233,10 +243,7 @@ export const ImportExceptionListFlyout = React.memo(
label={i18n.IMPORT_EXCEPTION_LIST_OVERWRITE}
checked={overwrite}
data-test-subj="importExceptionListOverwriteExistingCheckbox"
- onChange={(e) => {
- setOverwrite(!overwrite);
- setAsNewList(false);
- }}
+ onChange={handleOverwriteCheckboxChange}
/>
{isEndpointExceptionsMovedFFEnabled ? (
{
- setAsNewList(!asNewList);
- setOverwrite(false);
- }}
+ onChange={handleNewListCheckboxChange}
/>
) : (
{
- setAsNewList(!asNewList);
- setOverwrite(false);
- }}
+ onChange={handleNewListCheckboxChange}
/>
)}
diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
index 9b0803324fbc9..b9d897326d927 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_import_flyout.tsx
@@ -46,12 +46,16 @@ const ArtifactImportErrorToast: React.FC<{
errors: BulkErrorSchema[];
onShowErrors: (errors: BulkErrorSchema[]) => void;
}> = ({ text, buttonLabel, errors, onShowErrors }) => {
+ const handleOnClick = useCallback(() => {
+ onShowErrors(errors);
+ }, [errors, onShowErrors]);
+
return (
<>
{text}
- onShowErrors(errors)}>
+
{buttonLabel}