From 776fe46e4334192f5590914067664b6d9c5acf04 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20S=C3=A1nchez?=
Date: Thu, 10 Mar 2022 11:50:49 +0100
Subject: [PATCH] [Security Solution] [Endpoint] Creates generic policy tab
artifact component to be used for all of our artifacts (#126685)
* Initial commit adding a generic component for policy artifact tabs
* Adds labels and translations. Also fixes loading state using isRefetching
* Fix checks and wrong count update when removing/adding items
* Fixes translations and adds extra privileges checks
* Use hook to retrieve url params instead of redux
* Fixes wrong loading state in flyout
* Extracts conditional artifact logic from generic components and adds an artifact_layout unit test
* Include new changes in policy tabs component
* Adds policy artifact flyout unit test
* Adds policy artifacts list unit test
* Adds policy artifacts delete modal unit test
* Adds external privileges checks on unit tests
* Uses FormattedMessage to include EuiLink inside the translation
* Uses FormattedMessage
* Generate new ExceptionsListApiClient instance when http changes
* Removes existing policy tab artifacts code in favor of generic component
* Update translation files
* Include pr suggestions
* Uses useUrlPagination hook for pagination
* Fix checks
* Fixes uni test
* Reorder use_list_artifact hook params and move custom getInstance functions inside a useCallback
* Fix typos and added pr feedback
* Fix typo in searchableFields props and vars
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../artifacts/use_list_artifact.test.tsx | 4 +-
.../hooks/artifacts/use_list_artifact.tsx | 34 +-
.../hooks/artifacts/use_summary_artifact.tsx | 8 +-
.../host_isolation_exceptions/constants.ts | 7 +
.../host_isolation_exceptions/view/hooks.ts | 3 +-
.../store/policy_details/action/index.ts | 3 +-
.../action/policy_trusted_apps_action.ts | 91 ---
.../store/policy_details/middleware/index.ts | 6 -
.../policy_trusted_apps_middleware.ts | 443 -------------
.../store/policy_details/reducer/index.ts | 3 +-
.../reducer/initial_policy_details_state.ts | 9 -
.../reducer/trusted_apps_reducer.test.ts | 272 --------
.../reducer/trusted_apps_reducer.ts | 124 ----
.../store/policy_details/selectors/index.ts | 1 -
.../selectors/trusted_apps_selectors.test.ts | 589 ------------------
.../selectors/trusted_apps_selectors.ts | 265 --------
.../public/management/pages/policy/types.ts | 20 -
.../delete_modal}/index.ts | 3 +-
.../policy_artifacts_delete_modal.test.tsx} | 23 +-
.../policy_artifacts_delete_modal.tsx | 88 +++
.../artifacts/delete_modal/translations.ts | 64 ++
.../policy/view/artifacts/empty/index.ts | 13 +
.../policy_artifacts_empty_unassigned.tsx | 80 +++
.../policy_artifacts_empty_unexisting.tsx | 59 ++
.../view/artifacts/empty/translations.ts | 48 ++
.../use_policy_artifacts_empty_hooks.ts} | 39 +-
.../list => artifacts/flyout}/index.ts | 3 +-
.../flyout/policy_artifacts_flyout.test.tsx} | 44 +-
.../flyout/policy_artifacts_flyout.tsx | 236 +++++++
.../view/artifacts/flyout/translations.ts | 95 +++
.../flyout => artifacts/layout}/index.ts | 3 +-
.../layout/policy_artifacts_layout.test.tsx | 191 ++++++
.../layout/policy_artifacts_layout.tsx | 247 ++++++++
.../view/artifacts/layout/translations.ts | 34 +
.../delete_modal => artifacts/list}/index.ts | 3 +-
.../list/policy_artifacts_list.test.tsx} | 64 +-
.../artifacts/list/policy_artifacts_list.tsx | 207 ++++++
.../view/artifacts/list/translations.ts | 36 ++
.../policy/view/artifacts/translations.ts | 88 +++
.../policy_event_filters_delete_modal.tsx | 118 ----
.../policy/view/event_filters/empty/index.ts | 9 -
.../policy_event_filters_empty_unassigned.tsx | 81 ---
.../policy_event_filters_empty_unexisting.tsx | 53 --
.../use_policy_event_filters_empty_hooks.ts | 65 --
.../flyout/policy_event_filters_flyout.tsx | 281 ---------
.../pages/policy/view/event_filters/hooks.ts | 161 -----
.../policy_event_filters_layout.test.tsx | 128 ----
.../layout/policy_event_filters_layout.tsx | 180 ------
.../list/policy_event_filters_list.tsx | 198 ------
.../components/assign_flyout.test.tsx | 289 ---------
.../components/assign_flyout.tsx | 323 ----------
.../components/delete_modal.test.tsx | 121 ----
.../components/delete_modal.tsx | 131 ----
.../components/empty_unassigned.tsx | 76 ---
.../components/empty_unexisting.tsx | 55 --
.../components/list.test.tsx | 220 -------
.../components/list.tsx | 225 -------
.../host_isolation_exceptions_tab.test.tsx | 165 -----
.../host_isolation_exceptions_tab.tsx | 191 ------
.../pages/policy/view/policy_hooks.ts | 106 +---
.../view/tabs/event_filters_translations.ts | 152 +++++
.../host_isolation_exceptions_translations.ts | 166 +++++
.../pages/policy/view/tabs/policy_tabs.tsx | 106 +++-
.../view/tabs/trusted_apps_translations.ts | 152 +++++
.../policy/view/trusted_apps/empty/index.ts | 9 -
.../policy_trusted_apps_empty_unassigned.tsx | 80 ---
.../policy_trusted_apps_empty_unexisting.tsx | 53 --
.../use_policy_trusted_apps_empty_hooks.ts | 65 --
.../policy/view/trusted_apps/flyout/index.ts | 8 -
.../policy_trusted_apps_flyout.test.tsx | 192 ------
.../flyout/policy_trusted_apps_flyout.tsx | 264 --------
.../pages/policy/view/trusted_apps/index.ts | 8 -
.../policy/view/trusted_apps/layout/index.ts | 8 -
.../policy_trusted_apps_layout.test.tsx | 198 ------
.../layout/policy_trusted_apps_layout.tsx | 179 ------
.../list/policy_trusted_apps_list.test.tsx | 346 ----------
.../list/policy_trusted_apps_list.tsx | 285 ---------
...ove_trusted_app_from_policy_modal.test.tsx | 258 --------
.../remove_trusted_app_from_policy_modal.tsx | 156 -----
.../exceptions_list_api_client.test.ts | 19 +
.../exceptions_list_api_client.ts | 9 +-
.../translations/translations/ja-JP.json | 96 +--
.../translations/translations/zh-CN.json | 97 +--
83 files changed, 2289 insertions(+), 7343 deletions(-)
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.test.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.test.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.ts
rename x-pack/plugins/security_solution/public/management/pages/policy/view/{event_filters/layout => artifacts/delete_modal}/index.ts (63%)
rename x-pack/plugins/security_solution/public/management/pages/policy/view/{event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx => artifacts/delete_modal/policy_artifacts_delete_modal.test.tsx} (84%)
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/translations.ts
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/index.ts
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/translations.ts
rename x-pack/plugins/security_solution/public/management/pages/policy/view/{host_isolation_exceptions/components/use_policy_host_isolation_exceptions_empty_hooks.ts => artifacts/empty/use_policy_artifacts_empty_hooks.ts} (60%)
rename x-pack/plugins/security_solution/public/management/pages/policy/view/{event_filters/list => artifacts/flyout}/index.ts (65%)
rename x-pack/plugins/security_solution/public/management/pages/policy/view/{event_filters/flyout/policy_event_filters_flyout.test.tsx => artifacts/flyout/policy_artifacts_flyout.test.tsx} (85%)
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/translations.ts
rename x-pack/plugins/security_solution/public/management/pages/policy/view/{event_filters/flyout => artifacts/layout}/index.ts (65%)
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/translations.ts
rename x-pack/plugins/security_solution/public/management/pages/policy/view/{event_filters/delete_modal => artifacts/list}/index.ts (67%)
rename x-pack/plugins/security_solution/public/management/pages/policy/view/{event_filters/list/policy_event_filters_list.test.tsx => artifacts/list/policy_artifacts_list.test.tsx} (64%)
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/translations.ts
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/translations.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/empty/index.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/empty/policy_event_filters_empty_unassigned.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/empty/policy_event_filters_empty_unexisting.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/empty/use_policy_event_filters_empty_hooks.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/policy_event_filters_flyout.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/hooks.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/layout/policy_event_filters_layout.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/layout/policy_event_filters_layout.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/assign_flyout.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/assign_flyout.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/delete_modal.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unassigned.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unexisting.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.tsx
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/event_filters_translations.ts
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/host_isolation_exceptions_translations.ts
create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/trusted_apps_translations.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/index.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unexisting.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/use_policy_trusted_apps_empty_hooks.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/index.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/index.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/index.ts
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/remove_trusted_app_from_policy_modal.tsx
diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.test.tsx
index e7f389735056e..e41660daa5c59 100644
--- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.test.tsx
@@ -56,7 +56,7 @@ describe('List artifact hook', () => {
result = await renderQuery(
() =>
- useListArtifact(instance, searchableFields, options, {
+ useListArtifact(instance, options, searchableFields, {
onSuccess: onSuccessMock,
retry: false,
}),
@@ -92,7 +92,7 @@ describe('List artifact hook', () => {
result = await renderQuery(
() =>
- useListArtifact(instance, searchableFields, options, {
+ useListArtifact(instance, options, searchableFields, {
onError: onErrorMock,
retry: false,
}),
diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.tsx
index 9ac894649d602..32aa0b26daa1d 100644
--- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.tsx
+++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_list_artifact.tsx
@@ -7,35 +7,45 @@
import { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { HttpFetchError } from 'kibana/public';
import { QueryObserverResult, useQuery, UseQueryOptions } from 'react-query';
+import { DEFAULT_EXCEPTION_LIST_ITEM_SEARCHABLE_FIELDS } from '../../../../common/endpoint/service/artifacts/constants';
+import { MaybeImmutable } from '../../../../common/endpoint/types';
import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE } from '../../common/constants';
import { parsePoliciesAndFilterToKql, parseQueryFilterToKQL } from '../../common/utils';
import { ExceptionsListApiClient } from '../../services/exceptions_list/exceptions_list_api_client';
+const DEFAULT_OPTIONS = Object.freeze({});
+
export function useListArtifact(
exceptionListApiClient: ExceptionsListApiClient,
- searcheableFields: string[],
- options: {
- filter: string;
- page: number;
- perPage: number;
- policies: string[];
- } = {
+ options: Partial<{
+ filter?: string;
+ page?: number;
+ perPage?: number;
+ policies?: string[];
+ excludedPolicies?: string[];
+ }> = {
filter: '',
- page: MANAGEMENT_DEFAULT_PAGE,
+ page: MANAGEMENT_DEFAULT_PAGE + 1,
perPage: MANAGEMENT_DEFAULT_PAGE_SIZE,
policies: [],
+ excludedPolicies: [],
},
- customQueryOptions: UseQueryOptions
+ searchableFields: MaybeImmutable = DEFAULT_EXCEPTION_LIST_ITEM_SEARCHABLE_FIELDS,
+ customQueryOptions: Partial<
+ UseQueryOptions
+ > = DEFAULT_OPTIONS,
+ customQueryIds: string[] = []
): QueryObserverResult {
- const { filter, page, perPage, policies } = options;
+ const { filter, page, perPage, policies, excludedPolicies } = options;
return useQuery(
- ['list', exceptionListApiClient, options],
+ [...customQueryIds, 'list', exceptionListApiClient, options],
() => {
return exceptionListApiClient.find({
filter: parsePoliciesAndFilterToKql({
policies,
- kuery: parseQueryFilterToKQL(filter, searcheableFields),
+ excludedPolicies,
+ kuery: parseQueryFilterToKQL(filter, searchableFields),
}),
perPage,
page,
diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_summary_artifact.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_summary_artifact.tsx
index e068ab650d391..9e4ca1682f022 100644
--- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_summary_artifact.tsx
+++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_summary_artifact.tsx
@@ -10,9 +10,11 @@ import { QueryObserverResult, useQuery, UseQueryOptions } from 'react-query';
import { parsePoliciesAndFilterToKql, parseQueryFilterToKQL } from '../../common/utils';
import { ExceptionsListApiClient } from '../../services/exceptions_list/exceptions_list_api_client';
+const DEFAULT_OPTIONS = Object.freeze({});
+
export function useSummaryArtifact(
exceptionListApiClient: ExceptionsListApiClient,
- searcheableFields: string[],
+ searchableFields: string[],
options: {
filter: string;
policies: string[];
@@ -20,7 +22,7 @@ export function useSummaryArtifact(
filter: '',
policies: [],
},
- customQueryOptions: UseQueryOptions
+ customQueryOptions: UseQueryOptions = DEFAULT_OPTIONS
): QueryObserverResult {
const { filter, policies } = options;
@@ -30,7 +32,7 @@ export function useSummaryArtifact(
return exceptionListApiClient.summary(
parsePoliciesAndFilterToKql({
policies,
- kuery: parseQueryFilterToKQL(filter, searcheableFields),
+ kuery: parseQueryFilterToKQL(filter, searchableFields),
})
);
},
diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts
index 8ba18f3df976c..9fe60fe6508be 100644
--- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/constants.ts
@@ -15,6 +15,13 @@ import {
ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME,
} from '@kbn/securitysolution-list-constants';
+export const SEARCHABLE_FIELDS: Readonly = [
+ `item_id`,
+ `name`,
+ `description`,
+ `entries.value`,
+];
+
export const HOST_ISOLATION_EXCEPTIONS_LIST_TYPE: ExceptionListType =
ExceptionListTypeEnum.ENDPOINT_HOST_ISOLATION_EXCEPTIONS;
diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts
index 7ab03f9eaa68f..6b043822968f5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts
@@ -23,6 +23,7 @@ import {
} from '../../../common/constants';
import { getHostIsolationExceptionsListPath } from '../../../common/routing';
import { parsePoliciesAndFilterToKql, parseQueryFilterToKQL } from '../../../common/utils';
+import { SEARCHABLE_FIELDS } from '../constants';
import {
getHostIsolationExceptionItems,
getHostIsolationExceptionSummary,
@@ -85,8 +86,6 @@ export function useCanSeeHostIsolationExceptionsMenu(): boolean {
return canSeeMenu;
}
-const SEARCHABLE_FIELDS: Readonly = [`item_id`, `name`, `description`, `entries.value`];
-
export function useFetchHostIsolationExceptionsList({
filter,
page,
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/index.ts
index ab84bb4f253ea..2be4735685454 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/index.ts
@@ -6,6 +6,5 @@
*/
import { PolicySettingsAction } from './policy_settings_action';
-import { PolicyTrustedAppsAction } from './policy_trusted_apps_action';
-export type PolicyDetailsAction = PolicySettingsAction | PolicyTrustedAppsAction;
+export type PolicyDetailsAction = PolicySettingsAction;
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts
deleted file mode 100644
index 3b27c7cd1b27c..0000000000000
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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 { Action } from 'redux';
-import { AsyncResourceState } from '../../../../../state';
-import {
- PutTrustedAppUpdateResponse,
- GetTrustedAppsListResponse,
- TrustedApp,
- MaybeImmutable,
-} from '../../../../../../../common/endpoint/types';
-import { PolicyArtifactsState } from '../../../types';
-
-export interface PolicyArtifactsAssignableListPageDataChanged {
- type: 'policyArtifactsAssignableListPageDataChanged';
- payload: AsyncResourceState;
-}
-
-export interface PolicyArtifactsUpdateTrustedApps {
- type: 'policyArtifactsUpdateTrustedApps';
- payload: {
- action: 'assign' | 'remove';
- artifacts: MaybeImmutable;
- };
-}
-
-export interface PolicyArtifactsUpdateTrustedAppsChanged {
- type: 'policyArtifactsUpdateTrustedAppsChanged';
- payload: AsyncResourceState;
-}
-
-export interface PolicyArtifactsAssignableListExistDataChanged {
- type: 'policyArtifactsAssignableListExistDataChanged';
- payload: AsyncResourceState;
-}
-
-export interface PolicyArtifactsAssignableListPageDataFilter {
- type: 'policyArtifactsAssignableListPageDataFilter';
- payload: { filter: string };
-}
-
-export interface PolicyArtifactsDeosAnyTrustedAppExists {
- type: 'policyArtifactsDeosAnyTrustedAppExists';
- payload: AsyncResourceState;
-}
-
-export interface PolicyArtifactsHasTrustedApps {
- type: 'policyArtifactsHasTrustedApps';
- payload: AsyncResourceState;
-}
-
-export interface AssignedTrustedAppsListStateChanged
- extends Action<'assignedTrustedAppsListStateChanged'> {
- payload: PolicyArtifactsState['assignedList'];
-}
-
-export interface PolicyDetailsListOfAllPoliciesStateChanged
- extends Action<'policyDetailsListOfAllPoliciesStateChanged'> {
- payload: PolicyArtifactsState['policies'];
-}
-
-export type PolicyDetailsTrustedAppsForceListDataRefresh =
- Action<'policyDetailsTrustedAppsForceListDataRefresh'>;
-
-export type PolicyDetailsArtifactsResetRemove = Action<'policyDetailsArtifactsResetRemove'>;
-
-export interface PolicyDetailsTrustedAppsRemoveListStateChanged
- extends Action<'policyDetailsTrustedAppsRemoveListStateChanged'> {
- payload: PolicyArtifactsState['removeList'];
-}
-
-/**
- * All of the possible actions for Trusted Apps under the Policy Details store
- */
-export type PolicyTrustedAppsAction =
- | PolicyArtifactsAssignableListPageDataChanged
- | PolicyArtifactsUpdateTrustedApps
- | PolicyArtifactsUpdateTrustedAppsChanged
- | PolicyArtifactsAssignableListExistDataChanged
- | PolicyArtifactsAssignableListPageDataFilter
- | PolicyArtifactsDeosAnyTrustedAppExists
- | PolicyArtifactsHasTrustedApps
- | AssignedTrustedAppsListStateChanged
- | PolicyDetailsListOfAllPoliciesStateChanged
- | PolicyDetailsTrustedAppsForceListDataRefresh
- | PolicyDetailsTrustedAppsRemoveListStateChanged
- | PolicyDetailsArtifactsResetRemove;
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/index.ts
index a5752ed1658d3..2409193d63819 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/index.ts
@@ -7,24 +7,18 @@
import { ImmutableMiddlewareFactory } from '../../../../../../common/store';
import { MiddlewareRunnerContext, PolicyDetailsState } from '../../../types';
-import { policyTrustedAppsMiddlewareRunner } from './policy_trusted_apps_middleware';
import { policySettingsMiddlewareRunner } from './policy_settings_middleware';
-import { TrustedAppsHttpService } from '../../../../trusted_apps/service';
export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory = (
coreStart
) => {
- // Initialize services needed by Policy middleware
- const trustedAppsService = new TrustedAppsHttpService(coreStart.http);
const middlewareContext: MiddlewareRunnerContext = {
coreStart,
- trustedAppsService,
};
return (store) => (next) => async (action) => {
next(action);
policySettingsMiddlewareRunner(middlewareContext, store, action);
- policyTrustedAppsMiddlewareRunner(middlewareContext, store, action);
};
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts
deleted file mode 100644
index e8a647c257b01..0000000000000
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts
+++ /dev/null
@@ -1,443 +0,0 @@
-/*
- * 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 { isEmpty } from 'lodash/fp';
-import {
- GetPolicyListResponse,
- MiddlewareRunner,
- MiddlewareRunnerContext,
- PolicyAssignedTrustedApps,
- PolicyDetailsState,
- PolicyDetailsStore,
- PolicyRemoveTrustedApps,
-} from '../../../types';
-import {
- doesPolicyTrustedAppsListNeedUpdate,
- getCurrentArtifactsLocation,
- getCurrentPolicyAssignedTrustedAppsState,
- getCurrentTrustedAppsRemoveListState,
- getCurrentUrlLocationPaginationParams,
- getLatestLoadedPolicyAssignedTrustedAppsState,
- getTrustedAppsIsRemoving,
- getTrustedAppsPolicyListState,
- isOnPolicyTrustedAppsView,
- isPolicyTrustedAppListLoading,
- licensedPolicy,
- policyIdFromParams,
- getDoesAnyTrustedAppExistsIsLoading,
-} from '../selectors';
-import {
- GetTrustedAppsListResponse,
- Immutable,
- MaybeImmutable,
- PutTrustedAppUpdateResponse,
- TrustedApp,
-} from '../../../../../../../common/endpoint/types';
-import { ImmutableMiddlewareAPI } from '../../../../../../common/store';
-import { TrustedAppsService } from '../../../../trusted_apps/service';
-import {
- asStaleResourceState,
- createFailedResourceState,
- createLoadedResourceState,
- createLoadingResourceState,
- isLoadingResourceState,
- isUninitialisedResourceState,
- isLoadedResourceState,
-} from '../../../../../state';
-import { parseQueryFilterToKQL } from '../../../../../common/utils';
-import { SEARCHABLE_FIELDS } from '../../../../trusted_apps/constants';
-import { PolicyDetailsAction } from '../action';
-import { ServerApiError } from '../../../../../../common/types';
-
-/** Runs all middleware actions associated with the Trusted Apps view in Policy Details */
-export const policyTrustedAppsMiddlewareRunner: MiddlewareRunner = async (
- context,
- store,
- action
-) => {
- const state = store.getState();
-
- /* -----------------------------------------------------------
- If not on the Trusted Apps Policy view, then just return
- ----------------------------------------------------------- */
- if (!isOnPolicyTrustedAppsView(state)) {
- return;
- }
-
- const { trustedAppsService } = context;
-
- switch (action.type) {
- case 'userChangedUrl':
- fetchPolicyTrustedAppsIfNeeded(context, store);
- fetchAllPoliciesIfNeeded(context, store);
-
- if (action.type === 'userChangedUrl' && getCurrentArtifactsLocation(state).show === 'list') {
- await searchTrustedApps(store, trustedAppsService);
- }
-
- break;
-
- case 'policyDetailsTrustedAppsForceListDataRefresh':
- fetchPolicyTrustedAppsIfNeeded(context, store, true);
- break;
-
- case 'policyArtifactsUpdateTrustedApps':
- if (
- getCurrentArtifactsLocation(state).show === 'list' &&
- action.payload.action === 'assign'
- ) {
- await updateTrustedApps(store, trustedAppsService, action.payload.artifacts);
- } else if (action.payload.action === 'remove') {
- removeTrustedAppsFromPolicy(context, store, action.payload.artifacts);
- }
-
- break;
-
- case 'policyArtifactsAssignableListPageDataFilter':
- if (getCurrentArtifactsLocation(state).show === 'list') {
- await searchTrustedApps(store, trustedAppsService, action.payload.filter);
- }
-
- break;
- }
-};
-
-const checkIfThereAreAssignableTrustedApps = async (
- store: ImmutableMiddlewareAPI,
- trustedAppsService: TrustedAppsService
-) => {
- const state = store.getState();
- const policyId = policyIdFromParams(state);
-
- store.dispatch({
- type: 'policyArtifactsAssignableListExistDataChanged',
- payload: createLoadingResourceState(),
- });
- try {
- const trustedApps = await trustedAppsService.getTrustedAppsList({
- page: 1,
- per_page: 100,
- kuery: `(not exception-list-agnostic.attributes.tags:"policy:${policyId}") AND (not exception-list-agnostic.attributes.tags:"policy:all")`,
- });
-
- store.dispatch({
- type: 'policyArtifactsAssignableListExistDataChanged',
- payload: createLoadedResourceState(!isEmpty(trustedApps.data)),
- });
- } catch (err) {
- store.dispatch({
- type: 'policyArtifactsAssignableListExistDataChanged',
- payload: createFailedResourceState(err.body ?? err),
- });
- }
-};
-
-const checkIfPolicyHasTrustedAppsAssigned = async (
- store: ImmutableMiddlewareAPI,
- trustedAppsService: TrustedAppsService
-) => {
- const state = store.getState();
- if (isLoadingResourceState(state.artifacts.hasTrustedApps)) {
- return;
- }
- if (isLoadedResourceState(state.artifacts.hasTrustedApps)) {
- store.dispatch({
- type: 'policyArtifactsHasTrustedApps',
- payload: createLoadingResourceState(state.artifacts.hasTrustedApps),
- });
- } else {
- store.dispatch({
- type: 'policyArtifactsHasTrustedApps',
- payload: createLoadingResourceState(),
- });
- }
- try {
- const policyId = policyIdFromParams(state);
- const kuery = `(exception-list-agnostic.attributes.tags:"policy:${policyId}" OR exception-list-agnostic.attributes.tags:"policy:all")`;
- const trustedApps = await trustedAppsService.getTrustedAppsList({
- page: 1,
- per_page: 100,
- kuery,
- });
-
- if (
- !trustedApps.total &&
- isUninitialisedResourceState(state.artifacts.doesAnyTrustedAppExists)
- ) {
- await checkIfAnyTrustedApp(store, trustedAppsService);
- }
-
- store.dispatch({
- type: 'policyArtifactsHasTrustedApps',
- payload: createLoadedResourceState(trustedApps),
- });
- } catch (err) {
- store.dispatch({
- type: 'policyArtifactsHasTrustedApps',
- payload: createFailedResourceState(err.body ?? err),
- });
- }
-};
-
-const checkIfAnyTrustedApp = async (
- store: ImmutableMiddlewareAPI,
- trustedAppsService: TrustedAppsService
-) => {
- const state = store.getState();
- if (getDoesAnyTrustedAppExistsIsLoading(state)) {
- return;
- }
- store.dispatch({
- type: 'policyArtifactsDeosAnyTrustedAppExists',
- payload: createLoadingResourceState(),
- });
- try {
- const trustedApps = await trustedAppsService.getTrustedAppsList({
- page: 1,
- per_page: 100,
- });
-
- store.dispatch({
- type: 'policyArtifactsDeosAnyTrustedAppExists',
- payload: createLoadedResourceState(trustedApps),
- });
- } catch (err) {
- store.dispatch({
- type: 'policyArtifactsDeosAnyTrustedAppExists',
- payload: createFailedResourceState(err.body ?? err),
- });
- }
-};
-
-const searchTrustedApps = async (
- store: ImmutableMiddlewareAPI,
- trustedAppsService: TrustedAppsService,
- filter?: string
-) => {
- const state = store.getState();
- const policyId = policyIdFromParams(state);
-
- store.dispatch({
- type: 'policyArtifactsAssignableListPageDataChanged',
- payload: createLoadingResourceState(),
- });
-
- try {
- const kuery = [
- `(not exception-list-agnostic.attributes.tags:"policy:${policyId}") AND (not exception-list-agnostic.attributes.tags:"policy:all")`,
- ];
-
- if (filter) {
- const filterKuery = parseQueryFilterToKQL(filter, SEARCHABLE_FIELDS) || undefined;
- if (filterKuery) {
- kuery.push(filterKuery);
- }
- }
-
- const trustedApps = await trustedAppsService.getTrustedAppsList({
- page: 1,
- per_page: 100,
- kuery: kuery.join(' AND '),
- });
-
- store.dispatch({
- type: 'policyArtifactsAssignableListPageDataChanged',
- payload: createLoadedResourceState(trustedApps),
- });
-
- if (isEmpty(trustedApps.data)) {
- checkIfThereAreAssignableTrustedApps(store, trustedAppsService);
- }
- } catch (err) {
- store.dispatch({
- type: 'policyArtifactsAssignableListPageDataChanged',
- payload: createFailedResourceState(err.body ?? err),
- });
- }
-};
-
-const updateTrustedApps = async (
- store: ImmutableMiddlewareAPI,
- trustedAppsService: TrustedAppsService,
- trustedApps: MaybeImmutable
-) => {
- const state = store.getState();
- const policyId = policyIdFromParams(state);
-
- store.dispatch({
- type: 'policyArtifactsUpdateTrustedAppsChanged',
- payload: createLoadingResourceState(),
- });
-
- try {
- const updatedTrustedApps = await trustedAppsService.assignPolicyToTrustedApps(
- policyId,
- trustedApps
- );
- await checkIfPolicyHasTrustedAppsAssigned(store, trustedAppsService);
-
- store.dispatch({
- type: 'policyArtifactsUpdateTrustedAppsChanged',
- payload: createLoadedResourceState(updatedTrustedApps),
- });
-
- store.dispatch({ type: 'policyDetailsTrustedAppsForceListDataRefresh' });
- } catch (err) {
- store.dispatch({
- type: 'policyArtifactsUpdateTrustedAppsChanged',
- payload: createFailedResourceState(err.body ?? err),
- });
- }
-};
-
-const fetchPolicyTrustedAppsIfNeeded = async (
- { trustedAppsService }: MiddlewareRunnerContext,
- { getState, dispatch }: PolicyDetailsStore,
- forceFetch: boolean = false
-) => {
- const state = getState();
-
- if (isPolicyTrustedAppListLoading(state)) {
- return;
- }
-
- if (forceFetch || doesPolicyTrustedAppsListNeedUpdate(state)) {
- dispatch({
- type: 'assignedTrustedAppsListStateChanged',
- payload: createLoadingResourceState(
- asStaleResourceState(getCurrentPolicyAssignedTrustedAppsState(state))
- ),
- });
-
- try {
- const urlLocationData = getCurrentUrlLocationPaginationParams(state);
- const policyId = policyIdFromParams(state);
- const kuery = [
- `((exception-list-agnostic.attributes.tags:"policy:${policyId}") OR (exception-list-agnostic.attributes.tags:"policy:all"))`,
- ];
-
- if (urlLocationData.filter) {
- const filterKuery =
- parseQueryFilterToKQL(urlLocationData.filter, SEARCHABLE_FIELDS) || undefined;
- if (filterKuery) {
- kuery.push(filterKuery);
- }
- }
- const fetchResponse = await trustedAppsService.getTrustedAppsList({
- page: urlLocationData.page_index + 1,
- per_page: urlLocationData.page_size,
- kuery: kuery.join(' AND '),
- });
-
- dispatch({
- type: 'assignedTrustedAppsListStateChanged',
- payload: createLoadedResourceState>({
- location: urlLocationData,
- artifacts: fetchResponse,
- }),
- });
-
- if (isUninitialisedResourceState(state.artifacts.hasTrustedApps)) {
- await checkIfPolicyHasTrustedAppsAssigned({ getState, dispatch }, trustedAppsService);
- }
- } catch (error) {
- dispatch({
- type: 'assignedTrustedAppsListStateChanged',
- payload: createFailedResourceState>(
- error as ServerApiError,
- getLatestLoadedPolicyAssignedTrustedAppsState(getState())
- ),
- });
- }
- }
-};
-
-const fetchAllPoliciesIfNeeded = async (
- { trustedAppsService }: MiddlewareRunnerContext,
- { getState, dispatch }: PolicyDetailsStore
-) => {
- const state = getState();
- const currentPoliciesState = getTrustedAppsPolicyListState(state);
- const isLoading = isLoadingResourceState(currentPoliciesState);
- const hasBeenLoaded = !isUninitialisedResourceState(currentPoliciesState);
-
- if (isLoading || hasBeenLoaded) {
- return;
- }
-
- dispatch({
- type: 'policyDetailsListOfAllPoliciesStateChanged',
- // @ts-expect-error ts 4.5 upgrade
- payload: createLoadingResourceState(asStaleResourceState(currentPoliciesState)),
- });
-
- try {
- const policyList = await trustedAppsService.getPolicyList({
- query: {
- page: 1,
- perPage: 1000,
- },
- });
-
- dispatch({
- type: 'policyDetailsListOfAllPoliciesStateChanged',
- payload: createLoadedResourceState(policyList),
- });
- } catch (error) {
- dispatch({
- type: 'policyDetailsListOfAllPoliciesStateChanged',
- payload: createFailedResourceState(error.body || error),
- });
- }
-};
-
-const removeTrustedAppsFromPolicy = async (
- { trustedAppsService }: MiddlewareRunnerContext,
- { getState, dispatch }: PolicyDetailsStore,
- trustedApps: MaybeImmutable
-): Promise => {
- const state = getState();
-
- if (getTrustedAppsIsRemoving(state)) {
- return;
- }
-
- dispatch({
- type: 'policyDetailsTrustedAppsRemoveListStateChanged',
- payload: createLoadingResourceState(
- asStaleResourceState(getCurrentTrustedAppsRemoveListState(state))
- ),
- });
-
- try {
- const currentPolicyId = licensedPolicy(state)?.id;
-
- if (!currentPolicyId) {
- throw new Error('current policy id not found');
- }
-
- const response = await trustedAppsService.removePolicyFromTrustedApps(
- currentPolicyId,
- trustedApps
- );
- await checkIfPolicyHasTrustedAppsAssigned({ getState, dispatch }, trustedAppsService);
-
- dispatch({
- type: 'policyDetailsTrustedAppsRemoveListStateChanged',
- payload: createLoadedResourceState({ artifacts: trustedApps, response }),
- });
-
- dispatch({
- type: 'policyDetailsTrustedAppsForceListDataRefresh',
- });
- } catch (error) {
- dispatch({
- type: 'policyDetailsTrustedAppsRemoveListStateChanged',
- payload: createFailedResourceState(error.body || error),
- });
- }
-};
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/index.ts
index a577c1ca85ef0..264f315be1898 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/index.ts
@@ -10,7 +10,6 @@ import { PolicyDetailsState } from '../../../types';
import { AppAction } from '../../../../../../common/store/actions';
import { policySettingsReducer } from './policy_settings_reducer';
import { initialPolicyDetailsState } from './initial_policy_details_state';
-import { policyTrustedAppsReducer } from './trusted_apps_reducer';
export * from './initial_policy_details_state';
@@ -18,7 +17,7 @@ export const policyDetailsReducer: ImmutableReducer {
- return [policySettingsReducer, policyTrustedAppsReducer].reduce(
+ return [policySettingsReducer].reduce(
(updatedState, runReducer) => runReducer(updatedState, action),
state
);
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts
index a1e63bc889dd6..23bd0f9a56d3e 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts
@@ -11,7 +11,6 @@ import {
MANAGEMENT_DEFAULT_PAGE,
MANAGEMENT_DEFAULT_PAGE_SIZE,
} from '../../../../../common/constants';
-import { createUninitialisedResourceState } from '../../../../../state';
/**
* Return a fresh copy of initial state, since we mutate state in the reducer.
@@ -34,13 +33,5 @@ export const initialPolicyDetailsState: () => Immutable = ()
show: undefined,
filter: '',
},
- assignableList: createUninitialisedResourceState(),
- trustedAppsToUpdate: createUninitialisedResourceState(),
- assignableListEntriesExist: createUninitialisedResourceState(),
- doesAnyTrustedAppExists: createUninitialisedResourceState(),
- hasTrustedApps: createUninitialisedResourceState(),
- assignedList: createUninitialisedResourceState(),
- policies: createUninitialisedResourceState(),
- removeList: createUninitialisedResourceState(),
},
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.test.ts
deleted file mode 100644
index e1d2fab6dcdb6..0000000000000
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.test.ts
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * 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 { PolicyDetailsState } from '../../../types';
-import { initialPolicyDetailsState } from './initial_policy_details_state';
-import { policyTrustedAppsReducer } from './trusted_apps_reducer';
-
-import { ImmutableObject } from '../../../../../../../common/endpoint/types';
-import {
- createLoadedResourceState,
- createUninitialisedResourceState,
- createLoadingResourceState,
- createFailedResourceState,
-} from '../../../../../state';
-import { getMockListResponse, getAPIError, getMockCreateResponse } from '../../../test_utils';
-import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing';
-
-describe('policy trusted apps reducer', () => {
- let initialState: ImmutableObject;
-
- beforeEach(() => {
- initialState = {
- ...initialPolicyDetailsState(),
- location: {
- pathname: getPolicyDetailsArtifactsListPath('abc'),
- search: '',
- hash: '',
- },
- };
- });
-
- describe('PolicyTrustedApps', () => {
- describe('policyArtifactsAssignableListPageDataChanged', () => {
- it('sets assignable list uninitialised', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsAssignableListPageDataChanged',
- payload: createUninitialisedResourceState(),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableList: {
- type: 'UninitialisedResourceState',
- },
- },
- });
- });
- it('sets assignable list loading', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsAssignableListPageDataChanged',
- payload: createLoadingResourceState(createUninitialisedResourceState()),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableList: {
- previousState: {
- type: 'UninitialisedResourceState',
- },
- type: 'LoadingResourceState',
- },
- },
- });
- });
- it('sets assignable list loaded', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsAssignableListPageDataChanged',
- payload: createLoadedResourceState(getMockListResponse()),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableList: {
- data: getMockListResponse(),
- type: 'LoadedResourceState',
- },
- },
- });
- });
- it('sets assignable list failed', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsAssignableListPageDataChanged',
- payload: createFailedResourceState(getAPIError()),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableList: {
- type: 'FailedResourceState',
- error: getAPIError(),
- lastLoadedState: undefined,
- },
- },
- });
- });
- });
- });
-
- describe('policyArtifactsUpdateTrustedAppsChanged', () => {
- it('sets update trusted app uninitialised', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsUpdateTrustedAppsChanged',
- payload: createUninitialisedResourceState(),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: {
- type: 'UninitialisedResourceState',
- },
- },
- });
- });
- it('sets update trusted app loading', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsUpdateTrustedAppsChanged',
- payload: createLoadingResourceState(createUninitialisedResourceState()),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: {
- previousState: {
- type: 'UninitialisedResourceState',
- },
- type: 'LoadingResourceState',
- },
- },
- });
- });
- it('sets update trusted app loaded', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsUpdateTrustedAppsChanged',
- payload: createLoadedResourceState([getMockCreateResponse()]),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: {
- data: [getMockCreateResponse()],
- type: 'LoadedResourceState',
- },
- },
- });
- });
- it('sets update trusted app failed', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsUpdateTrustedAppsChanged',
- payload: createFailedResourceState(getAPIError()),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: {
- type: 'FailedResourceState',
- error: getAPIError(),
- lastLoadedState: undefined,
- },
- },
- });
- });
- });
-
- describe('policyArtifactsAssignableListExistDataChanged', () => {
- it('sets exists trusted app uninitialised', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsAssignableListExistDataChanged',
- payload: createUninitialisedResourceState(),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: {
- type: 'UninitialisedResourceState',
- },
- },
- });
- });
- it('sets exists trusted app loading', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsAssignableListExistDataChanged',
- payload: createLoadingResourceState(createUninitialisedResourceState()),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: {
- previousState: {
- type: 'UninitialisedResourceState',
- },
- type: 'LoadingResourceState',
- },
- },
- });
- });
- it('sets exists trusted app loaded negative', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsAssignableListExistDataChanged',
- payload: createLoadedResourceState(false),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: {
- data: false,
- type: 'LoadedResourceState',
- },
- },
- });
- });
- it('sets exists trusted app loaded positive', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsAssignableListExistDataChanged',
- payload: createLoadedResourceState(true),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: {
- data: true,
- type: 'LoadedResourceState',
- },
- },
- });
- });
- it('sets exists trusted app failed', () => {
- const result = policyTrustedAppsReducer(initialState, {
- type: 'policyArtifactsAssignableListExistDataChanged',
- payload: createFailedResourceState(getAPIError()),
- });
-
- expect(result).toStrictEqual({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: {
- type: 'FailedResourceState',
- error: getAPIError(),
- lastLoadedState: undefined,
- },
- },
- });
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts
deleted file mode 100644
index f601e3ef0afb4..0000000000000
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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 { ImmutableReducer } from '../../../../../../common/store';
-import { PolicyDetailsState } from '../../../types';
-import { AppAction } from '../../../../../../common/store/actions';
-import { initialPolicyDetailsState } from './initial_policy_details_state';
-import { isUninitialisedResourceState } from '../../../../../state';
-import { getCurrentPolicyAssignedTrustedAppsState, isOnPolicyTrustedAppsView } from '../selectors';
-
-export const policyTrustedAppsReducer: ImmutableReducer = (
- state = initialPolicyDetailsState(),
- action
-) => {
- /* ----------------------------------------------------------
- If not on the Trusted Apps Policy view, then just return
- ---------------------------------------------------------- */
- if (!isOnPolicyTrustedAppsView(state)) {
- // If the artifacts state namespace needs resetting, then do it now
- if (!isUninitialisedResourceState(getCurrentPolicyAssignedTrustedAppsState(state))) {
- return {
- ...state,
- artifacts: initialPolicyDetailsState().artifacts,
- };
- }
-
- return state;
- }
-
- if (action.type === 'policyArtifactsAssignableListPageDataChanged') {
- return {
- ...state,
- artifacts: {
- ...state.artifacts,
- assignableList: action.payload,
- },
- };
- }
-
- if (action.type === 'policyArtifactsUpdateTrustedAppsChanged') {
- return {
- ...state,
- artifacts: {
- ...state.artifacts,
- trustedAppsToUpdate: action.payload,
- },
- };
- }
-
- if (action.type === 'policyArtifactsAssignableListExistDataChanged') {
- return {
- ...state,
- artifacts: {
- ...state.artifacts,
- assignableListEntriesExist: action.payload,
- },
- };
- }
-
- if (action.type === 'policyArtifactsDeosAnyTrustedAppExists') {
- return {
- ...state,
- artifacts: {
- ...state?.artifacts,
- doesAnyTrustedAppExists: action.payload,
- },
- };
- }
-
- if (action.type === 'policyArtifactsHasTrustedApps') {
- return {
- ...state,
- artifacts: {
- ...state?.artifacts,
- hasTrustedApps: action.payload,
- },
- };
- }
- if (action.type === 'assignedTrustedAppsListStateChanged') {
- return {
- ...state,
- artifacts: {
- ...state?.artifacts,
- assignedList: action.payload,
- },
- };
- }
-
- if (action.type === 'policyDetailsListOfAllPoliciesStateChanged') {
- return {
- ...state,
- artifacts: {
- ...state.artifacts,
- policies: action.payload,
- },
- };
- }
-
- if (action.type === 'policyDetailsTrustedAppsRemoveListStateChanged') {
- return {
- ...state,
- artifacts: {
- ...state.artifacts,
- removeList: action.payload,
- },
- };
- }
-
- if (action.type === 'policyDetailsArtifactsResetRemove') {
- return {
- ...state,
- artifacts: {
- ...state.artifacts,
- removeList: initialPolicyDetailsState().artifacts.removeList,
- },
- };
- }
-
- return state;
-};
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/index.ts
index d9c167d4a801c..808791338ca7f 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/index.ts
@@ -6,5 +6,4 @@
*/
export * from './policy_settings_selectors';
-export * from './trusted_apps_selectors';
export * from './policy_common_selectors';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.test.ts
deleted file mode 100644
index 0fbd674b265b0..0000000000000
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.test.ts
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * 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 { PolicyArtifactsState, PolicyDetailsState } from '../../../types';
-import { initialPolicyDetailsState } from '../reducer';
-import {
- getAssignableArtifactsList,
- getAssignableArtifactsListIsLoading,
- getUpdateArtifactsIsLoading,
- getUpdateArtifactsIsFailed,
- getUpdateArtifactsLoaded,
- getAssignableArtifactsListExist,
- getAssignableArtifactsListExistIsLoading,
- getUpdateArtifacts,
- doesPolicyTrustedAppsListNeedUpdate,
- isPolicyTrustedAppListLoading,
- getPolicyTrustedAppList,
- getPolicyTrustedAppsListPagination,
- getTrustedAppsListOfAllPolicies,
- getTrustedAppsAllPoliciesById,
-} from './trusted_apps_selectors';
-import { getCurrentArtifactsLocation, isOnPolicyTrustedAppsView } from './policy_common_selectors';
-
-import { ImmutableObject } from '../../../../../../../common/endpoint/types';
-import {
- createLoadedResourceState,
- createUninitialisedResourceState,
- createLoadingResourceState,
- createFailedResourceState,
-} from '../../../../../state';
-import { MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH } from '../../../../../common/constants';
-import {
- getMockListResponse,
- getAPIError,
- getMockCreateResponse,
- getMockPolicyDetailsArtifactListUrlParams,
- getMockPolicyDetailsArtifactsPageLocationUrlParams,
-} from '../../../test_utils';
-import { getGeneratedPolicyResponse } from '../../../../trusted_apps/store/mocks';
-
-describe('policy trusted apps selectors', () => {
- let initialState: ImmutableObject;
-
- const createArtifactsState = (
- artifacts: Partial = {}
- ): ImmutableObject => {
- return {
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- ...artifacts,
- },
- };
- };
-
- beforeEach(() => {
- initialState = initialPolicyDetailsState();
- });
-
- describe('doesPolicyTrustedAppsListNeedUpdate()', () => {
- it('should return true if state is not loaded', () => {
- expect(doesPolicyTrustedAppsListNeedUpdate(initialState)).toBe(true);
- });
-
- it('should return true if it is loaded, but URL params were changed', () => {
- expect(
- doesPolicyTrustedAppsListNeedUpdate(
- createArtifactsState({
- location: getMockPolicyDetailsArtifactsPageLocationUrlParams({ page_index: 4 }),
- assignedList: createLoadedResourceState({
- location: getMockPolicyDetailsArtifactListUrlParams(),
- artifacts: getMockListResponse(),
- }),
- })
- )
- ).toBe(true);
- });
-
- it('should return false if state is loaded adn URL params are the same', () => {
- expect(
- doesPolicyTrustedAppsListNeedUpdate(
- createArtifactsState({
- location: getMockPolicyDetailsArtifactsPageLocationUrlParams(),
- assignedList: createLoadedResourceState({
- location: getMockPolicyDetailsArtifactListUrlParams(),
- artifacts: getMockListResponse(),
- }),
- })
- )
- ).toBe(false);
- });
- });
-
- describe('isPolicyTrustedAppListLoading()', () => {
- it('should return true when loading data', () => {
- expect(
- isPolicyTrustedAppListLoading(
- createArtifactsState({
- assignedList: createLoadingResourceState(createUninitialisedResourceState()),
- })
- )
- ).toBe(true);
- });
-
- it.each([
- ['uninitialized', createUninitialisedResourceState() as PolicyArtifactsState['assignedList']],
- ['loaded', createLoadedResourceState({}) as PolicyArtifactsState['assignedList']],
- ['failed', createFailedResourceState({}) as PolicyArtifactsState['assignedList']],
- ])('should return false when state is %s', (__, assignedListState) => {
- expect(
- isPolicyTrustedAppListLoading(createArtifactsState({ assignedList: assignedListState }))
- ).toBe(false);
- });
- });
-
- describe('getPolicyTrustedAppList()', () => {
- it('should return the list of trusted apps', () => {
- const listResponse = getMockListResponse();
-
- expect(
- getPolicyTrustedAppList(
- createArtifactsState({
- location: getMockPolicyDetailsArtifactsPageLocationUrlParams(),
- assignedList: createLoadedResourceState({
- location: getMockPolicyDetailsArtifactListUrlParams(),
- artifacts: listResponse,
- }),
- })
- )
- ).toEqual(listResponse.data);
- });
-
- it('should return empty array if no data is loaded', () => {
- expect(getPolicyTrustedAppList(initialState)).toEqual([]);
- });
- });
-
- describe('getPolicyTrustedAppsListPagination()', () => {
- it('should return default pagination data even if no api data is available', () => {
- expect(getPolicyTrustedAppsListPagination(initialState)).toEqual({
- pageIndex: 0,
- pageSize: 10,
- pageSizeOptions: [10, 20, 50],
- totalItemCount: 0,
- });
- });
-
- it('should return pagination data based on api response data', () => {
- const listResponse = getMockListResponse();
-
- listResponse.page = 6;
- listResponse.per_page = 100;
- listResponse.total = 1000;
-
- expect(
- getPolicyTrustedAppsListPagination(
- createArtifactsState({
- location: getMockPolicyDetailsArtifactsPageLocationUrlParams({
- page_index: 5,
- page_size: 100,
- }),
- assignedList: createLoadedResourceState({
- location: getMockPolicyDetailsArtifactListUrlParams({
- page_index: 5,
- page_size: 100,
- }),
- artifacts: listResponse,
- }),
- })
- )
- ).toEqual({
- pageIndex: 5,
- pageSize: 100,
- pageSizeOptions: [10, 20, 50],
- totalItemCount: 1000,
- });
- });
- });
-
- describe('getTrustedAppsListOfAllPolicies()', () => {
- it('should return the loaded list of policies', () => {
- const policiesApiResponse = getGeneratedPolicyResponse();
-
- expect(
- getTrustedAppsListOfAllPolicies(
- createArtifactsState({
- policies: createLoadedResourceState(policiesApiResponse),
- })
- )
- ).toEqual(policiesApiResponse.items);
- });
-
- it('should return an empty array of no policy data was loaded yet', () => {
- expect(getTrustedAppsListOfAllPolicies(initialState)).toEqual([]);
- });
- });
-
- describe('getTrustedAppsAllPoliciesById()', () => {
- it('should return an empty object if no polices', () => {
- expect(getTrustedAppsAllPoliciesById(initialState)).toEqual({});
- });
-
- it('should return an object with policy id and policy data', () => {
- const policiesApiResponse = getGeneratedPolicyResponse();
-
- expect(
- getTrustedAppsAllPoliciesById(
- createArtifactsState({
- policies: createLoadedResourceState(policiesApiResponse),
- })
- )
- ).toEqual({ [policiesApiResponse.items[0].id]: policiesApiResponse.items[0] });
- });
- });
-
- describe('isOnPolicyTrustedAppsPage()', () => {
- it('when location is on policy trusted apps page', () => {
- const isOnPage = isOnPolicyTrustedAppsView({
- ...initialState,
- location: {
- pathname: MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
- search: '',
- hash: '',
- },
- });
- expect(isOnPage).toBeFalsy();
- });
- it('when location is not on policy trusted apps page', () => {
- const isOnPage = isOnPolicyTrustedAppsView({
- ...initialState,
- location: { pathname: '', search: '', hash: '' },
- });
- expect(isOnPage).toBeFalsy();
- });
- });
-
- describe('getCurrentArtifactsLocation()', () => {
- it('when location is defined', () => {
- const location = getCurrentArtifactsLocation(initialState);
- expect(location).toEqual({ filter: '', page_index: 0, page_size: 10, show: undefined });
- });
- it('when location has show param to list', () => {
- const location = getCurrentArtifactsLocation({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- location: { ...initialState.artifacts.location, show: 'list' },
- },
- });
- expect(location).toEqual({ filter: '', page_index: 0, page_size: 10, show: 'list' });
- });
- });
-
- describe('getAssignableArtifactsList()', () => {
- it('when assignable list is uninitialised', () => {
- const assignableList = getAssignableArtifactsList(initialState);
- expect(assignableList).toBeUndefined();
- });
- it('when assignable list is loading', () => {
- const assignableList = getAssignableArtifactsList({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableList: createLoadingResourceState(createUninitialisedResourceState()),
- },
- });
- expect(assignableList).toBeUndefined();
- });
- it('when assignable list is loaded', () => {
- const assignableList = getAssignableArtifactsList({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableList: createLoadedResourceState(getMockListResponse()),
- },
- });
- expect(assignableList).toEqual(getMockListResponse());
- });
- });
-
- describe('getAssignableArtifactsListIsLoading()', () => {
- it('when assignable list is loading', () => {
- const isLoading = getAssignableArtifactsListIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableList: createLoadingResourceState(createUninitialisedResourceState()),
- },
- });
- expect(isLoading).toBeTruthy();
- });
- it('when assignable list is uninitialised', () => {
- const isLoading = getAssignableArtifactsListIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableList: createUninitialisedResourceState(),
- },
- });
- expect(isLoading).toBeFalsy();
- });
- it('when assignable list is loaded', () => {
- const isLoading = getAssignableArtifactsListIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableList: createLoadedResourceState(getMockListResponse()),
- },
- });
- expect(isLoading).toBeFalsy();
- });
- });
-
- describe('getUpdateArtifactsIsLoading()', () => {
- it('when update artifacts is loading', () => {
- const isLoading = getUpdateArtifactsIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createLoadingResourceState(createUninitialisedResourceState()),
- },
- });
- expect(isLoading).toBeTruthy();
- });
- it('when update artifacts is uninitialised', () => {
- const isLoading = getUpdateArtifactsIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createUninitialisedResourceState(),
- },
- });
- expect(isLoading).toBeFalsy();
- });
- it('when update artifacts is loaded', () => {
- const isLoading = getUpdateArtifactsIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createLoadedResourceState([getMockCreateResponse()]),
- },
- });
- expect(isLoading).toBeFalsy();
- });
- });
-
- describe('getUpdateArtifactsIsFailed()', () => {
- it('when update artifacts is loading', () => {
- const hasFailed = getUpdateArtifactsIsFailed({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createLoadingResourceState(createUninitialisedResourceState()),
- },
- });
- expect(hasFailed).toBeFalsy();
- });
- it('when update artifacts is uninitialised', () => {
- const hasFailed = getUpdateArtifactsIsFailed({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createUninitialisedResourceState(),
- },
- });
- expect(hasFailed).toBeFalsy();
- });
- it('when update artifacts is loaded', () => {
- const hasFailed = getUpdateArtifactsIsFailed({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createLoadedResourceState([getMockCreateResponse()]),
- },
- });
- expect(hasFailed).toBeFalsy();
- });
- it('when update artifacts has failed', () => {
- const hasFailed = getUpdateArtifactsIsFailed({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createFailedResourceState(getAPIError()),
- },
- });
- expect(hasFailed).toBeTruthy();
- });
- });
-
- describe('getUpdateArtifactsLoaded()', () => {
- it('when update artifacts is loading', () => {
- const isLoaded = getUpdateArtifactsLoaded({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createLoadingResourceState(createUninitialisedResourceState()),
- },
- });
- expect(isLoaded).toBeFalsy();
- });
- it('when update artifacts is uninitialised', () => {
- const isLoaded = getUpdateArtifactsLoaded({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createUninitialisedResourceState(),
- },
- });
- expect(isLoaded).toBeFalsy();
- });
- it('when update artifacts is loaded', () => {
- const isLoaded = getUpdateArtifactsLoaded({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createLoadedResourceState([getMockCreateResponse()]),
- },
- });
- expect(isLoaded).toBeTruthy();
- });
- it('when update artifacts has failed', () => {
- const isLoaded = getUpdateArtifactsLoaded({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createFailedResourceState(getAPIError()),
- },
- });
- expect(isLoaded).toBeFalsy();
- });
- });
-
- describe('getUpdateArtifacts()', () => {
- it('when update artifacts is loading', () => {
- const isLoading = getUpdateArtifacts({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createLoadingResourceState(createUninitialisedResourceState()),
- },
- });
- expect(isLoading).toBeUndefined();
- });
- it('when update artifacts is uninitialised', () => {
- const isLoading = getUpdateArtifacts({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createUninitialisedResourceState(),
- },
- });
- expect(isLoading).toBeUndefined();
- });
- it('when update artifacts is loaded', () => {
- const isLoading = getUpdateArtifacts({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createLoadedResourceState([getMockCreateResponse()]),
- },
- });
- expect(isLoading).toEqual([getMockCreateResponse()]);
- });
- it('when update artifacts has failed', () => {
- const isLoading = getUpdateArtifacts({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- trustedAppsToUpdate: createFailedResourceState(getAPIError()),
- },
- });
- expect(isLoading).toBeUndefined();
- });
- });
-
- describe('getAssignableArtifactsListExist()', () => {
- it('when check artifacts exists is loading', () => {
- const exists = getAssignableArtifactsListExist({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createLoadingResourceState(
- createUninitialisedResourceState()
- ),
- },
- });
- expect(exists).toBeFalsy();
- });
- it('when check artifacts exists is uninitialised', () => {
- const exists = getAssignableArtifactsListExist({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createUninitialisedResourceState(),
- },
- });
- expect(exists).toBeFalsy();
- });
- it('when check artifacts exists is loaded with negative result', () => {
- const exists = getAssignableArtifactsListExist({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createLoadedResourceState(false),
- },
- });
- expect(exists).toBeFalsy();
- });
- it('when check artifacts exists is loaded with positive result', () => {
- const exists = getAssignableArtifactsListExist({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createLoadedResourceState(true),
- },
- });
- expect(exists).toBeTruthy();
- });
- it('when check artifacts exists has failed', () => {
- const exists = getAssignableArtifactsListExist({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createFailedResourceState(getAPIError()),
- },
- });
- expect(exists).toBeFalsy();
- });
- });
-
- describe('getAssignableArtifactsListExistIsLoading()', () => {
- it('when check artifacts exists is loading', () => {
- const isLoading = getAssignableArtifactsListExistIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createLoadingResourceState(
- createUninitialisedResourceState()
- ),
- },
- });
- expect(isLoading).toBeTruthy();
- });
- it('when check artifacts exists is uninitialised', () => {
- const isLoading = getAssignableArtifactsListExistIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createUninitialisedResourceState(),
- },
- });
- expect(isLoading).toBeFalsy();
- });
- it('when check artifacts exists is loaded with negative result', () => {
- const isLoading = getAssignableArtifactsListExistIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createLoadedResourceState(false),
- },
- });
- expect(isLoading).toBeFalsy();
- });
- it('when check artifacts exists is loaded with positive result', () => {
- const isLoading = getAssignableArtifactsListExistIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createLoadedResourceState(true),
- },
- });
- expect(isLoading).toBeFalsy();
- });
- it('when check artifacts exists has failed', () => {
- const isLoading = getAssignableArtifactsListExistIsLoading({
- ...initialState,
- artifacts: {
- ...initialState.artifacts,
- assignableListEntriesExist: createFailedResourceState(getAPIError()),
- },
- });
- expect(isLoading).toBeFalsy();
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.ts
deleted file mode 100644
index d341b8ae7a180..0000000000000
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/trusted_apps_selectors.ts
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * 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 { createSelector } from 'reselect';
-import { Pagination } from '@elastic/eui';
-import { isEmpty } from 'lodash/fp';
-import {
- PolicyArtifactsState,
- PolicyAssignedTrustedApps,
- PolicyDetailsArtifactsPageListLocationParams,
- PolicyDetailsSelector,
- PolicyDetailsState,
-} from '../../../types';
-import {
- Immutable,
- ImmutableArray,
- PostTrustedAppCreateResponse,
- GetTrustedAppsListResponse,
- PolicyData,
-} from '../../../../../../../common/endpoint/types';
-import { MANAGEMENT_PAGE_SIZE_OPTIONS } from '../../../../../common/constants';
-import {
- getLastLoadedResourceState,
- isFailedResourceState,
- isLoadedResourceState,
- isLoadingResourceState,
- LoadedResourceState,
-} from '../../../../../state';
-import { getCurrentArtifactsLocation } from './policy_common_selectors';
-import { ServerApiError } from '../../../../../../common/types';
-
-export const doesPolicyHaveTrustedAppsAssignedList = (
- state: PolicyDetailsState
-): { loading: boolean; hasTrustedApps: boolean } => {
- return {
- loading: isLoadingResourceState(state.artifacts.assignedList),
- hasTrustedApps: isLoadedResourceState(state.artifacts.assignedList)
- ? !isEmpty(state.artifacts.assignedList.data.artifacts.data)
- : false,
- };
-};
-
-/**
- * Returns current assignable artifacts list
- */
-export const getAssignableArtifactsList = (
- state: Immutable
-): Immutable | undefined =>
- getLastLoadedResourceState(state.artifacts.assignableList)?.data;
-
-/**
- * Returns if assignable list is loading
- */
-export const getAssignableArtifactsListIsLoading = (
- state: Immutable
-): boolean => isLoadingResourceState(state.artifacts.assignableList);
-
-/**
- * Returns if update action is loading
- */
-export const getUpdateArtifactsIsLoading = (state: Immutable): boolean =>
- isLoadingResourceState(state.artifacts.trustedAppsToUpdate);
-
-/**
- * Returns if update action is loading
- */
-export const getUpdateArtifactsIsFailed = (state: Immutable): boolean =>
- isFailedResourceState(state.artifacts.trustedAppsToUpdate);
-
-/**
- * Returns if update action is done successfully
- */
-export const getUpdateArtifactsLoaded = (state: Immutable): boolean => {
- return isLoadedResourceState(state.artifacts.trustedAppsToUpdate);
-};
-
-/**
- * Returns true if there is data assignable even if the search didn't returned it.
- */
-export const getAssignableArtifactsListExist = (state: Immutable): boolean => {
- return (
- isLoadedResourceState(state.artifacts.assignableListEntriesExist) &&
- state.artifacts.assignableListEntriesExist.data
- );
-};
-
-/**
- * Returns true if there is data assignable even if the search didn't returned it.
- */
-export const getAssignableArtifactsListExistIsLoading = (
- state: Immutable
-): boolean => {
- return isLoadingResourceState(state.artifacts.assignableListEntriesExist);
-};
-
-/**
- * Returns artifacts to be updated
- */
-export const getUpdateArtifacts = (
- state: Immutable
-): ImmutableArray | undefined => {
- return state.artifacts.trustedAppsToUpdate.type === 'LoadedResourceState'
- ? state.artifacts.trustedAppsToUpdate.data
- : undefined;
-};
-
-/**
- * Returns does any TA exists
- */
-export const getDoesTrustedAppExists = (state: Immutable): boolean => {
- return (
- isLoadedResourceState(state.artifacts.doesAnyTrustedAppExists) &&
- !!state.artifacts.doesAnyTrustedAppExists.data.total
- );
-};
-
-/**
- * Returns does any TA exists loading
- */
-export const doesTrustedAppExistsLoading = (state: Immutable): boolean => {
- return isLoadingResourceState(state.artifacts.doesAnyTrustedAppExists);
-};
-
-/** Returns a boolean of whether the user is on the policy details page or not */
-export const getCurrentPolicyAssignedTrustedAppsState: PolicyDetailsSelector<
- PolicyArtifactsState['assignedList']
-> = (state) => {
- return state.artifacts.assignedList;
-};
-
-/** Returns current filter value */
-export const getCurrentPolicyArtifactsFilter: PolicyDetailsSelector = (state) => {
- return state.artifacts.location.filter;
-};
-
-export const getLatestLoadedPolicyAssignedTrustedAppsState: PolicyDetailsSelector<
- undefined | LoadedResourceState
-> = createSelector(getCurrentPolicyAssignedTrustedAppsState, (currentAssignedTrustedAppsState) => {
- return getLastLoadedResourceState(currentAssignedTrustedAppsState);
-});
-
-export const getCurrentUrlLocationPaginationParams: PolicyDetailsSelector =
- // eslint-disable-next-line @typescript-eslint/naming-convention
- createSelector(getCurrentArtifactsLocation, ({ filter, page_index, page_size }) => {
- return { filter, page_index, page_size };
- });
-
-export const doesPolicyTrustedAppsListNeedUpdate: PolicyDetailsSelector = createSelector(
- getCurrentPolicyAssignedTrustedAppsState,
- getCurrentUrlLocationPaginationParams,
- (assignedListState, locationData) => {
- return (
- !isLoadedResourceState(assignedListState) ||
- (isLoadedResourceState(assignedListState) &&
- (
- Object.keys(locationData) as Array
- ).some((key) => assignedListState.data.location[key] !== locationData[key]))
- );
- }
-);
-
-export const isPolicyTrustedAppListLoading: PolicyDetailsSelector = createSelector(
- getCurrentPolicyAssignedTrustedAppsState,
- (assignedState) => isLoadingResourceState(assignedState)
-);
-
-export const getPolicyTrustedAppList: PolicyDetailsSelector =
- createSelector(getLatestLoadedPolicyAssignedTrustedAppsState, (assignedState) => {
- return assignedState?.data.artifacts.data ?? [];
- });
-
-export const getPolicyTrustedAppsListPagination: PolicyDetailsSelector = createSelector(
- getLatestLoadedPolicyAssignedTrustedAppsState,
- (currentAssignedTrustedAppsState) => {
- const trustedAppsApiResponse = currentAssignedTrustedAppsState?.data.artifacts;
-
- return {
- // Trusted apps api is `1` based for page - need to subtract here for `Pagination` component
- pageIndex: trustedAppsApiResponse?.page ? trustedAppsApiResponse.page - 1 : 0,
- pageSize: trustedAppsApiResponse?.per_page ?? MANAGEMENT_PAGE_SIZE_OPTIONS[0],
- totalItemCount: trustedAppsApiResponse?.total || 0,
- pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS],
- };
- }
-);
-
-export const getTotalPolicyTrustedAppsListPagination = (
- state: Immutable
-): number => {
- return getLastLoadedResourceState(state.artifacts.hasTrustedApps)?.data.total || 0;
-};
-
-export const getTrustedAppsPolicyListState: PolicyDetailsSelector<
- PolicyDetailsState['artifacts']['policies']
-> = (state) => state.artifacts.policies;
-
-export const getTrustedAppsListOfAllPolicies: PolicyDetailsSelector = createSelector(
- getTrustedAppsPolicyListState,
- (policyListState) => {
- return getLastLoadedResourceState(policyListState)?.data.items ?? [];
- }
-);
-
-export const getTrustedAppsAllPoliciesById: PolicyDetailsSelector<
- Record>
-> = createSelector(getTrustedAppsListOfAllPolicies, (allPolicies) => {
- return allPolicies.reduce>>((mapById, policy) => {
- mapById[policy.id] = policy;
- return mapById;
- }, {}) as Immutable>>;
-});
-
-export const getHasTrustedApps: PolicyDetailsSelector = (state) => {
- return !!getLastLoadedResourceState(state.artifacts.hasTrustedApps)?.data.total;
-};
-
-export const getIsLoadedHasTrustedApps: PolicyDetailsSelector = (state) =>
- !!getLastLoadedResourceState(state.artifacts.hasTrustedApps);
-
-export const getHasTrustedAppsIsLoading: PolicyDetailsSelector = (state) =>
- isLoadingResourceState(state.artifacts.hasTrustedApps);
-
-export const getDoesAnyTrustedAppExists: PolicyDetailsSelector<
- PolicyDetailsState['artifacts']['doesAnyTrustedAppExists']
-> = (state) => state.artifacts.doesAnyTrustedAppExists;
-
-export const getDoesAnyTrustedAppExistsIsLoading: PolicyDetailsSelector = createSelector(
- getDoesAnyTrustedAppExists,
- (doesAnyTrustedAppExists) => {
- return isLoadingResourceState(doesAnyTrustedAppExists);
- }
-);
-
-export const getPolicyTrustedAppListError: PolicyDetailsSelector<
- Immutable | undefined
-> = createSelector(getCurrentPolicyAssignedTrustedAppsState, (currentAssignedTrustedAppsState) => {
- if (isFailedResourceState(currentAssignedTrustedAppsState)) {
- return currentAssignedTrustedAppsState.error;
- }
-});
-
-export const getCurrentTrustedAppsRemoveListState: PolicyDetailsSelector<
- PolicyArtifactsState['removeList']
-> = (state) => state.artifacts.removeList;
-
-export const getTrustedAppsIsRemoving: PolicyDetailsSelector = createSelector(
- getCurrentTrustedAppsRemoveListState,
- (removeListState) => isLoadingResourceState(removeListState)
-);
-
-export const getTrustedAppsRemovalError: PolicyDetailsSelector =
- createSelector(getCurrentTrustedAppsRemoveListState, (removeListState) => {
- if (isFailedResourceState(removeListState)) {
- return removeListState.error;
- }
- });
-
-export const getTrustedAppsWasRemoveSuccessful: PolicyDetailsSelector = createSelector(
- getCurrentTrustedAppsRemoveListState,
- (removeListState) => isLoadedResourceState(removeListState)
-);
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts
index bb511c886c83c..6057c0545fa63 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts
@@ -13,7 +13,6 @@ import {
ProtectionFields,
PolicyData,
UIPolicyConfig,
- PostTrustedAppCreateResponse,
MaybeImmutable,
GetTrustedAppsListResponse,
TrustedApp,
@@ -26,10 +25,8 @@ import {
GetPackagePoliciesResponse,
UpdatePackagePolicyResponse,
} from '../../../../../fleet/common';
-import { AsyncResourceState } from '../../state';
import { ImmutableMiddlewareAPI } from '../../../common/store';
import { AppAction } from '../../../common/store/actions';
-import { TrustedAppsService } from '../trusted_apps/service';
export type PolicyDetailsStore = ImmutableMiddlewareAPI;
@@ -44,7 +41,6 @@ export type MiddlewareRunner = (
export interface MiddlewareRunnerContext {
coreStart: CoreStart;
- trustedAppsService: TrustedAppsService;
}
export type PolicyDetailsSelector = (
@@ -91,22 +87,6 @@ export interface PolicyRemoveTrustedApps {
export interface PolicyArtifactsState {
/** artifacts location params */
location: PolicyDetailsArtifactsPageLocation;
- /** A list of artifacts can be linked to the policy */
- assignableList: AsyncResourceState;
- /** Represents if available trusted apps entries exist, regardless of whether the list is showing results */
- assignableListEntriesExist: AsyncResourceState;
- /** A list of trusted apps going to be updated */
- trustedAppsToUpdate: AsyncResourceState;
- /** Represents if there is any trusted app existing */
- doesAnyTrustedAppExists: AsyncResourceState;
- /** Represents if there is any trusted app existing assigned to the policy (without filters) */
- hasTrustedApps: AsyncResourceState;
- /** List of artifacts currently assigned to the policy (body specific and global) */
- assignedList: AsyncResourceState;
- /** A list of all available polices */
- policies: AsyncResourceState;
- /** list of artifacts to remove. Holds the ids that were removed and the API response */
- removeList: AsyncResourceState;
}
export interface PolicyDetailsArtifactsPageListLocationParams {
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/layout/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/index.ts
similarity index 63%
rename from x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/layout/index.ts
rename to x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/index.ts
index c35cb26dfe8fc..4a8db92171414 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/layout/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/index.ts
@@ -5,4 +5,5 @@
* 2.0.
*/
-export { PolicyEventFiltersLayout } from './policy_event_filters_layout';
+export { PolicyArtifactsDeleteModal } from './policy_artifacts_delete_modal';
+export { POLICY_ARTIFACT_DELETE_MODAL_LABELS } from './translations';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.test.tsx
similarity index 84%
rename from x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx
rename to x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.test.tsx
index 2e00dab303007..ed5553337d6ac 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/policy_event_filters_delete_modal.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.test.tsx
@@ -15,33 +15,36 @@ import {
AppContextTestRender,
createAppRootMockRenderer,
} from '../../../../../../common/mock/endpoint';
-import { PolicyEventFiltersDeleteModal } from './policy_event_filters_delete_modal';
+import { PolicyArtifactsDeleteModal } from './policy_artifacts_delete_modal';
import { eventFiltersListQueryHttpMock } from '../../../../event_filters/test_utils';
-import { cleanEventFilterToUpdate } from '../../../../event_filters/service/service_actions';
+import { EventFiltersApiClient } from '../../../../event_filters/service/event_filters_api_client';
+import { POLICY_ARTIFACT_DELETE_MODAL_LABELS } from './translations';
-describe('Policy details event filter delete modal', () => {
+describe('Policy details artifacts delete modal', () => {
let policyId: string;
let render: () => Promise>;
let renderResult: ReturnType;
let mockedContext: AppContextTestRender;
let exception: ExceptionListItemSchema;
let mockedApi: ReturnType;
- let onCancel: () => void;
+ let onCloseMock: () => jest.Mock;
beforeEach(() => {
policyId = uuid.v4();
mockedContext = createAppRootMockRenderer();
exception = getExceptionListItemSchemaMock();
- onCancel = jest.fn();
+ onCloseMock = jest.fn();
mockedApi = eventFiltersListQueryHttpMock(mockedContext.coreStart.http);
render = async () => {
await act(async () => {
renderResult = mockedContext.render(
-
);
await waitFor(mockedApi.responseProvider.eventFiltersList);
@@ -74,7 +77,7 @@ describe('Policy details event filter delete modal', () => {
await waitFor(() => {
expect(mockedApi.responseProvider.eventFiltersUpdateOne).toHaveBeenLastCalledWith({
body: JSON.stringify(
- cleanEventFilterToUpdate({
+ EventFiltersApiClient.cleanExceptionsBeforeUpdate({
...exception,
tags: ['policy:1234', 'policy:4321', 'not-a-policy-tag'],
})
@@ -93,7 +96,7 @@ describe('Policy details event filter delete modal', () => {
expect(mockedApi.responseProvider.eventFiltersUpdateOne).toHaveBeenCalled();
});
- expect(onCancel).toHaveBeenCalled();
+ expect(onCloseMock).toHaveBeenCalled();
expect(mockedContext.coreStart.notifications.toasts.addSuccess).toHaveBeenCalled();
});
@@ -112,7 +115,7 @@ describe('Policy details event filter delete modal', () => {
});
expect(mockedContext.coreStart.notifications.toasts.addError).toHaveBeenCalledWith(error, {
- title: 'Error while attempt to remove event filter',
+ title: 'Error while attempting to remove artifact',
});
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.tsx
new file mode 100644
index 0000000000000..a92411c22ce2b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/policy_artifacts_delete_modal.tsx
@@ -0,0 +1,88 @@
+/*
+ * 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 { EuiCallOut, EuiConfirmModal, EuiSpacer, EuiText } from '@elastic/eui';
+import { useQueryClient } from 'react-query';
+import { HttpFetchError } from 'kibana/public';
+import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
+import React, { useCallback } from 'react';
+import { useBulkUpdateArtifact } from '../../../../../hooks/artifacts';
+import { useToasts } from '../../../../../../common/lib/kibana';
+import { ExceptionsListApiClient } from '../../../../../services/exceptions_list/exceptions_list_api_client';
+import { BY_POLICY_ARTIFACT_TAG_PREFIX } from '../../../../../../../common/endpoint/service/artifacts';
+import { POLICY_ARTIFACT_DELETE_MODAL_LABELS } from './translations';
+
+interface PolicyArtifactsDeleteModalProps {
+ policyId: string;
+ policyName: string;
+ apiClient: ExceptionsListApiClient;
+ exception: ExceptionListItemSchema;
+ onClose: () => void;
+ labels: typeof POLICY_ARTIFACT_DELETE_MODAL_LABELS;
+}
+
+export const PolicyArtifactsDeleteModal = React.memo(
+ ({ policyId, policyName, apiClient, exception, onClose, labels }) => {
+ const toasts = useToasts();
+ const queryClient = useQueryClient();
+
+ const { mutate: updateArtifact, isLoading: isUpdateArtifactLoading } = useBulkUpdateArtifact(
+ apiClient,
+ {
+ onSuccess: () => {
+ toasts.addSuccess({
+ title: labels.deleteModalSuccessMessageTitle,
+ text: labels.deleteModalSuccessMessageText(exception, policyName),
+ });
+ queryClient.invalidateQueries(['list', apiClient]);
+ onClose();
+ },
+ onError: (error?: HttpFetchError) => {
+ toasts.addError(error as unknown as Error, {
+ title: labels.deleteModalErrorMessage,
+ });
+ },
+ }
+ );
+
+ const handleModalConfirm = useCallback(() => {
+ const modifiedException = {
+ ...exception,
+ tags: exception.tags.filter((tag) => tag !== `${BY_POLICY_ARTIFACT_TAG_PREFIX}${policyId}`),
+ };
+ updateArtifact([modifiedException]);
+ }, [exception, policyId, updateArtifact]);
+
+ const handleOnClose = useCallback(() => {
+ if (!isUpdateArtifactLoading) {
+ onClose();
+ }
+ }, [isUpdateArtifactLoading, onClose]);
+
+ return (
+
+
+ {labels.deleteModalImpactInfo}
+
+
+
+
+
+ {labels.deleteModalConfirmInfo}
+
+
+ );
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/translations.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/translations.ts
new file mode 100644
index 0000000000000..bbbe4ed73c295
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/delete_modal/translations.ts
@@ -0,0 +1,64 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
+
+export const POLICY_ARTIFACT_DELETE_MODAL_LABELS = Object.freeze({
+ deleteModalTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.list.removeDialog.title',
+ {
+ defaultMessage: 'Remove artifact from policy',
+ }
+ ),
+
+ deleteModalImpactInfo: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.list.removeDialog.messageCallout',
+ {
+ defaultMessage:
+ 'This artifact will be removed only from this policy and can still be found and managed from the artifact page.',
+ }
+ ),
+
+ deleteModalConfirmInfo: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.list.removeDialog.message',
+ {
+ defaultMessage: 'Are you sure you wish to continue?',
+ }
+ ),
+
+ deleteModalSubmitButtonTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.list.removeDialog.confirmLabel',
+ {
+ defaultMessage: 'Remove from policy',
+ }
+ ),
+
+ deleteModalCancelButtonTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.list.removeDialog.cancelLabel',
+ { defaultMessage: 'Cancel' }
+ ),
+
+ deleteModalSuccessMessageTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.list.removeDialog.successToastTitle',
+ { defaultMessage: 'Successfully removed' }
+ ),
+ deleteModalSuccessMessageText: (exception: ExceptionListItemSchema, policyName: string): string =>
+ i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.list.removeDialog.successToastText',
+ {
+ defaultMessage: '"{artifactName}" has been removed from {policyName} policy',
+ values: { artifactName: exception.name, policyName },
+ }
+ ),
+ deleteModalErrorMessage: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.list.removeDialog.errorToastTitle',
+ {
+ defaultMessage: 'Error while attempting to remove artifact',
+ }
+ ),
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/index.ts
new file mode 100644
index 0000000000000..db833460283cf
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/index.ts
@@ -0,0 +1,13 @@
+/*
+ * 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.
+ */
+
+export { PolicyArtifactsEmptyUnassigned } from './policy_artifacts_empty_unassigned';
+export { PolicyArtifactsEmptyUnexisting } from './policy_artifacts_empty_unexisting';
+export {
+ POLICY_ARTIFACT_EMPTY_UNASSIGNED_LABELS,
+ POLICY_ARTIFACT_EMPTY_UNEXISTING_LABELS,
+} from './translations';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx
new file mode 100644
index 0000000000000..5a606de45068d
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unassigned.tsx
@@ -0,0 +1,80 @@
+/*
+ * 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, { memo, useCallback } from 'react';
+import { EuiButton, EuiEmptyPrompt, EuiPageTemplate, EuiLink } from '@elastic/eui';
+import { usePolicyDetailsArtifactsNavigateCallback } from '../../policy_hooks';
+import { useGetLinkTo } from './use_policy_artifacts_empty_hooks';
+import { useUserPrivileges } from '../../../../../../common/components/user_privileges';
+import { POLICY_ARTIFACT_EMPTY_UNASSIGNED_LABELS } from './translations';
+import { EventFiltersPageLocation } from '../../../../event_filters/types';
+import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state';
+import { HostIsolationExceptionsPageLocation } from '../../../../host_isolation_exceptions/types';
+interface CommonProps {
+ policyId: string;
+ policyName: string;
+ listId: string;
+ labels: typeof POLICY_ARTIFACT_EMPTY_UNASSIGNED_LABELS;
+ getPolicyArtifactsPath: (policyId: string) => string;
+ getArtifactPath: (
+ location?:
+ | Partial
+ | Partial
+ | Partial
+ ) => string;
+}
+
+export const PolicyArtifactsEmptyUnassigned = memo(
+ ({ policyId, policyName, listId, labels, getPolicyArtifactsPath, getArtifactPath }) => {
+ const { canCreateArtifactsByPolicy } = useUserPrivileges().endpointPrivileges;
+ const { onClickHandler, toRouteUrl } = useGetLinkTo(
+ policyId,
+ policyName,
+ getPolicyArtifactsPath,
+ getArtifactPath
+ );
+
+ const navigateCallback = usePolicyDetailsArtifactsNavigateCallback(listId);
+ const onClickPrimaryButtonHandler = useCallback(
+ () =>
+ navigateCallback({
+ show: 'list',
+ }),
+ [navigateCallback]
+ );
+ return (
+
+ {labels.emptyUnassignedTitle}}
+ body={labels.emptyUnassignedMessage(policyName)}
+ actions={[
+ ...(canCreateArtifactsByPolicy
+ ? [
+
+ {labels.emptyUnassignedPrimaryActionButtonTitle}
+ ,
+ ]
+ : []),
+ // eslint-disable-next-line @elastic/eui/href-or-on-click
+
+ {labels.emptyUnassignedSecondaryActionButtonTitle}
+ ,
+ ]}
+ />
+
+ );
+ }
+);
+
+PolicyArtifactsEmptyUnassigned.displayName = 'PolicyArtifactsEmptyUnassigned';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx
new file mode 100644
index 0000000000000..c62db552e9a01
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/policy_artifacts_empty_unexisting.tsx
@@ -0,0 +1,59 @@
+/*
+ * 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, { memo } from 'react';
+import { EuiEmptyPrompt, EuiButton, EuiPageTemplate } from '@elastic/eui';
+import { useGetLinkTo } from './use_policy_artifacts_empty_hooks';
+import { POLICY_ARTIFACT_EMPTY_UNEXISTING_LABELS } from './translations';
+import { EventFiltersPageLocation } from '../../../../event_filters/types';
+import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state';
+import { HostIsolationExceptionsPageLocation } from '../../../../host_isolation_exceptions/types';
+
+interface CommonProps {
+ policyId: string;
+ policyName: string;
+ labels: typeof POLICY_ARTIFACT_EMPTY_UNEXISTING_LABELS;
+ getPolicyArtifactsPath: (policyId: string) => string;
+ getArtifactPath: (
+ location?:
+ | Partial
+ | Partial
+ | Partial
+ ) => string;
+}
+
+export const PolicyArtifactsEmptyUnexisting = memo(
+ ({ policyId, policyName, labels, getPolicyArtifactsPath, getArtifactPath }) => {
+ const { onClickHandler, toRouteUrl } = useGetLinkTo(
+ policyId,
+ policyName,
+ getPolicyArtifactsPath,
+ getArtifactPath,
+ {
+ show: 'create',
+ }
+ );
+ return (
+
+ {labels.emptyUnexistingTitle}}
+ body={labels.emptyUnexistingMessage}
+ actions={
+ // eslint-disable-next-line @elastic/eui/href-or-on-click
+
+ {labels.emptyUnexistingPrimaryActionButtonTitle}
+
+ }
+ />
+
+ );
+ }
+);
+
+PolicyArtifactsEmptyUnexisting.displayName = 'PolicyArtifactsEmptyUnexisting';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/translations.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/translations.ts
new file mode 100644
index 0000000000000..8fdb3f2dbfb5b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/translations.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const POLICY_ARTIFACT_EMPTY_UNASSIGNED_LABELS = Object.freeze({
+ emptyUnassignedTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.title',
+ { defaultMessage: 'No assigned artifacts' }
+ ),
+ emptyUnassignedMessage: (policyName: string): string =>
+ i18n.translate('xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.content', {
+ defaultMessage:
+ 'There are currently no artifacts assigned to {policyName}. Assign artifacts now or add and manage them on the artifacts page.',
+ values: { policyName },
+ }),
+ emptyUnassignedPrimaryActionButtonTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.primaryAction',
+ {
+ defaultMessage: 'Assign artifacts',
+ }
+ ),
+ emptyUnassignedSecondaryActionButtonTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.secondaryAction',
+ {
+ defaultMessage: 'Manage artifacts',
+ }
+ ),
+});
+
+export const POLICY_ARTIFACT_EMPTY_UNEXISTING_LABELS = Object.freeze({
+ emptyUnexistingTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.empty.unexisting.title',
+ { defaultMessage: 'No artifacts exist' }
+ ),
+ emptyUnexistingMessage: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.empty.unexisting.content',
+ { defaultMessage: 'There are currently no artifacts applied to your endpoints.' }
+ ),
+ emptyUnexistingPrimaryActionButtonTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.empty.unexisting.action',
+ { defaultMessage: 'Add artifacts' }
+ ),
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/use_policy_host_isolation_exceptions_empty_hooks.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/use_policy_artifacts_empty_hooks.ts
similarity index 60%
rename from x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/use_policy_host_isolation_exceptions_empty_hooks.ts
rename to x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/use_policy_artifacts_empty_hooks.ts
index 494dfd9a7ae08..2304cb7dfdd6d 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/use_policy_host_isolation_exceptions_empty_hooks.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/empty/use_policy_artifacts_empty_hooks.ts
@@ -9,35 +9,38 @@ import { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { useNavigateToAppEventHandler } from '../../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { useAppUrl } from '../../../../../../common/lib/kibana/hooks';
-import {
- getPolicyHostIsolationExceptionsPath,
- getHostIsolationExceptionsListPath,
-} from '../../../../../common/routing';
import { APP_UI_ID } from '../../../../../../../common/constants';
+import { EventFiltersPageLocation } from '../../../../event_filters/types';
+import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state';
import { HostIsolationExceptionsPageLocation } from '../../../../host_isolation_exceptions/types';
export const useGetLinkTo = (
policyId: string,
policyName: string,
- location?: Partial
+ getPolicyArtifactsPath: (policyId: string) => string,
+ getArtifactPath: (
+ location?:
+ | Partial
+ | Partial
+ | Partial
+ ) => string,
+ location?: Partial<{ show: 'create' }>
) => {
const { getAppUrl } = useAppUrl();
const { toRoutePath, toRouteUrl } = useMemo(() => {
- const path = getHostIsolationExceptionsListPath(location);
+ const path = getArtifactPath(location);
return {
toRoutePath: path,
toRouteUrl: getAppUrl({ path }),
};
- }, [getAppUrl, location]);
+ }, [getAppUrl, getArtifactPath, location]);
- const policyHostIsolationExceptionsPath = useMemo(
- () => getPolicyHostIsolationExceptionsPath(policyId),
- [policyId]
- );
- const policyHostIsolationExceptionsRouteState = useMemo(() => {
+ const policyArtifactsPath = getPolicyArtifactsPath(policyId);
+
+ const policyArtifactRouteState = useMemo(() => {
return {
backButtonLabel: i18n.translate(
- 'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unassigned.backButtonLabel',
+ 'xpack.securitySolution.endpoint.policy.artifacts.empty.unassigned.backButtonLabel',
{
defaultMessage: 'Back to {policyName} policy',
values: {
@@ -48,24 +51,24 @@ export const useGetLinkTo = (
onBackButtonNavigateTo: [
APP_UI_ID,
{
- path: policyHostIsolationExceptionsPath,
+ path: policyArtifactsPath,
},
],
backButtonUrl: getAppUrl({
appId: APP_UI_ID,
- path: policyHostIsolationExceptionsPath,
+ path: policyArtifactsPath,
}),
};
- }, [getAppUrl, policyName, policyHostIsolationExceptionsPath]);
+ }, [getAppUrl, policyName, policyArtifactsPath]);
const onClickHandler = useNavigateToAppEventHandler(APP_UI_ID, {
- state: policyHostIsolationExceptionsRouteState,
+ state: policyArtifactRouteState,
path: toRoutePath,
});
return {
onClickHandler,
toRouteUrl,
- state: policyHostIsolationExceptionsRouteState,
+ state: policyArtifactRouteState,
};
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/index.ts
similarity index 65%
rename from x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/index.ts
rename to x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/index.ts
index 9b13b6f5741a9..7337ccff2a836 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/index.ts
@@ -5,4 +5,5 @@
* 2.0.
*/
-export { PolicyEventFiltersList } from './policy_event_filters_list';
+export { PolicyArtifactsFlyout } from './policy_artifacts_flyout';
+export { POLICY_ARTIFACT_FLYOUT_LABELS } from './translations';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/policy_event_filters_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx
similarity index 85%
rename from x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/policy_event_filters_flyout.test.tsx
rename to x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx
index 7d984cdb2a382..e9ac077857284 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/policy_event_filters_flyout.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.test.tsx
@@ -17,8 +17,9 @@ import {
} from '../../../../../../common/mock/endpoint';
import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data';
import { PolicyData } from '../../../../../../../common/endpoint/types';
+import { MANAGEMENT_DEFAULT_PAGE } from '../../../../../common/constants';
import { eventFiltersListQueryHttpMock } from '../../../../event_filters/test_utils';
-import { PolicyEventFiltersFlyout } from './policy_event_filters_flyout';
+import { MAX_ALLOWED_RESULTS, PolicyArtifactsFlyout } from './policy_artifacts_flyout';
import { parseQueryFilterToKQL, parsePoliciesAndFilterToKql } from '../../../../../common/utils';
import { SEARCHABLE_FIELDS } from '../../../../event_filters/constants';
import {
@@ -26,6 +27,8 @@ import {
UpdateExceptionListItemSchema,
} from '@kbn/securitysolution-io-ts-list-types';
import { cleanEventFilterToUpdate } from '../../../../event_filters/service/service_actions';
+import { EventFiltersApiClient } from '../../../../event_filters/service/event_filters_api_client';
+import { POLICY_ARTIFACT_FLYOUT_LABELS } from './translations';
const getDefaultQueryParameters = (customFilter: string | undefined = '') => ({
path: '/api/exception_lists/items/_find',
@@ -33,8 +36,8 @@ const getDefaultQueryParameters = (customFilter: string | undefined = '') => ({
filter: customFilter,
list_id: ['endpoint_event_filters'],
namespace_type: ['agnostic'],
- page: undefined,
- per_page: 100,
+ page: MANAGEMENT_DEFAULT_PAGE + 1,
+ per_page: MAX_ALLOWED_RESULTS,
sort_field: undefined,
sort_order: undefined,
},
@@ -59,7 +62,7 @@ const getCleanedExceptionWithNewTags = (
return cleanEventFilterToUpdate(exceptionToUpdateWithNewTags);
};
-describe('Policy details event filters flyout', () => {
+describe('Policy details artifacts flyout', () => {
let render: () => Promise>;
let renderResult: ReturnType;
let mockedContext: AppContextTestRender;
@@ -76,7 +79,13 @@ describe('Policy details event filters flyout', () => {
render = async () => {
await act(async () => {
renderResult = mockedContext.render(
-
+
);
await waitFor(mockedApi.responseProvider.eventFiltersList);
});
@@ -89,7 +98,7 @@ describe('Policy details event filters flyout', () => {
return getFoundExceptionListItemSchemaMock(1);
});
await render();
- expect(mockedApi.responseProvider.eventFiltersList).toHaveBeenLastCalledWith(
+ expect(mockedApi.responseProvider.eventFiltersList).toHaveBeenCalledWith(
getDefaultQueryParameters(
parsePoliciesAndFilterToKql({
excludedPolicies: [policy.id, 'all'],
@@ -124,7 +133,7 @@ describe('Policy details event filters flyout', () => {
})
)
);
- expect(renderResult.getByTestId('eventFilters-no-items-found')).toBeTruthy();
+ expect(renderResult.getByTestId('artifacts-no-items-found')).toBeTruthy();
});
});
@@ -132,7 +141,7 @@ describe('Policy details event filters flyout', () => {
// both exceptions list requests will return no results
mockedApi.responseProvider.eventFiltersList.mockImplementation(() => getEmptyList());
await render();
- expect(await renderResult.findByTestId('eventFilters-no-assignable-items')).toBeTruthy();
+ expect(await renderResult.findByTestId('artifacts-no-assignable-items')).toBeTruthy();
});
it('should disable the submit button if no exceptions are selected', async () => {
@@ -141,7 +150,7 @@ describe('Policy details event filters flyout', () => {
});
await render();
expect(await renderResult.findByTestId('artifactsList')).toBeTruthy();
- expect(renderResult.getByTestId('eventFilters-assign-confirm-button')).toBeDisabled();
+ expect(renderResult.getByTestId('artifacts-assign-confirm-button')).toBeDisabled();
});
it('should enable the submit button if an exception is selected', async () => {
@@ -155,7 +164,7 @@ describe('Policy details event filters flyout', () => {
// click the first item
userEvent.click(renderResult.getByTestId(`${firstOneName}_checkbox`));
- expect(renderResult.getByTestId('eventFilters-assign-confirm-button')).toBeEnabled();
+ expect(renderResult.getByTestId('artifacts-assign-confirm-button')).toBeEnabled();
});
it('should warn the user when there are over 100 results in the flyout', async () => {
@@ -167,7 +176,7 @@ describe('Policy details event filters flyout', () => {
});
await render();
expect(await renderResult.findByTestId('artifactsList')).toBeTruthy();
- expect(renderResult.getByTestId('eventFilters-too-many-results')).toBeTruthy();
+ expect(renderResult.getByTestId('artifacts-too-many-results')).toBeTruthy();
});
describe('when submitting the form', () => {
@@ -205,7 +214,7 @@ describe('Policy details event filters flyout', () => {
// click the first item
userEvent.click(renderResult.getByTestId(`${FIRST_ONE_NAME}_checkbox`));
// submit the form
- userEvent.click(renderResult.getByTestId('eventFilters-assign-confirm-button'));
+ userEvent.click(renderResult.getByTestId('artifacts-assign-confirm-button'));
// verify the request with the new tag
await waitFor(() => {
@@ -219,7 +228,7 @@ describe('Policy details event filters flyout', () => {
await waitFor(() => {
expect(mockedContext.coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({
- text: `"${FIRST_ONE_NAME}" has been added to your event filters list.`,
+ text: `"${FIRST_ONE_NAME}" has been added to your artifacts list.`,
title: 'Success',
});
});
@@ -231,7 +240,7 @@ describe('Policy details event filters flyout', () => {
userEvent.click(renderResult.getByTestId(`${FIRST_ONE_NAME}_checkbox`));
userEvent.click(renderResult.getByTestId(`${SECOND_ONE_NAME}_checkbox`));
// submit the form
- userEvent.click(renderResult.getByTestId('eventFilters-assign-confirm-button'));
+ userEvent.click(renderResult.getByTestId('artifacts-assign-confirm-button'));
// verify the request with the new tag
await waitFor(() => {
@@ -253,27 +262,26 @@ describe('Policy details event filters flyout', () => {
await waitFor(() => {
expect(mockedContext.coreStart.notifications.toasts.addSuccess).toHaveBeenCalledWith({
- text: '2 event filters have been added to your list.',
+ text: '2 artifacts have been added to your list.',
title: 'Success',
});
});
expect(onCloseMock).toHaveBeenCalled();
});
- it('should show a toast error when the request fails and close the flyout', async () => {
+ it('should show a toast error when the request fails', async () => {
mockedApi.responseProvider.eventFiltersUpdateOne.mockImplementation(() => {
throw new Error('the server is too far away');
});
// click first item
userEvent.click(renderResult.getByTestId(`${FIRST_ONE_NAME}_checkbox`));
// submit the form
- userEvent.click(renderResult.getByTestId('eventFilters-assign-confirm-button'));
+ userEvent.click(renderResult.getByTestId('artifacts-assign-confirm-button'));
await waitFor(() => {
expect(mockedContext.coreStart.notifications.toasts.addDanger).toHaveBeenCalledWith(
'An error occurred updating artifacts'
);
- expect(onCloseMock).toHaveBeenCalled();
});
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.tsx
new file mode 100644
index 0000000000000..1ba31da565304
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/policy_artifacts_flyout.tsx
@@ -0,0 +1,236 @@
+/*
+ * 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, { useCallback, useMemo, useState } from 'react';
+import { useQueryClient } from 'react-query';
+import { isEmpty, without } from 'lodash/fp';
+import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
+import {
+ EuiTitle,
+ EuiFlyout,
+ EuiSpacer,
+ EuiFlyoutHeader,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiButtonEmpty,
+ EuiButton,
+ EuiCallOut,
+ EuiEmptyPrompt,
+} from '@elastic/eui';
+import { SearchExceptions } from '../../../../../components/search_exceptions';
+import { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types';
+import { useToasts } from '../../../../../../common/lib/kibana';
+import { PolicyArtifactsAssignableList } from '../../artifacts/assignable';
+import { ExceptionsListApiClient } from '../../../../../services/exceptions_list/exceptions_list_api_client';
+import { useListArtifact, useBulkUpdateArtifact } from '../../../../../hooks/artifacts';
+import { POLICY_ARTIFACT_FLYOUT_LABELS } from './translations';
+
+interface PolicyArtifactsFlyoutProps {
+ policyItem: ImmutableObject;
+ apiClient: ExceptionsListApiClient;
+ searchableFields: string[];
+ onClose: () => void;
+ labels: typeof POLICY_ARTIFACT_FLYOUT_LABELS;
+}
+
+export const MAX_ALLOWED_RESULTS = 100;
+
+export const PolicyArtifactsFlyout = React.memo(
+ ({ policyItem, apiClient, searchableFields, onClose, labels }) => {
+ const toasts = useToasts();
+ const queryClient = useQueryClient();
+ const [selectedArtifactIds, setSelectedArtifactIds] = useState([]);
+ const [currentFilter, setCurrentFilter] = useState('');
+
+ const bulkUpdateMutation = useBulkUpdateArtifact(apiClient, {
+ onSuccess: (updatedExceptions: ExceptionListItemSchema[]) => {
+ toasts.addSuccess({
+ title: labels.flyoutSuccessMessageTitle,
+ text: labels.flyoutSuccessMessageText(updatedExceptions),
+ });
+ queryClient.invalidateQueries(['list', apiClient]);
+ onClose();
+ },
+ onError: () => {
+ toasts.addDanger(labels.flyoutErrorMessage);
+ },
+ });
+
+ const {
+ data: artifacts,
+ isLoading: isLoadingArtifacts,
+ isRefetching: isRefetchingArtifacts,
+ } = useListArtifact(
+ apiClient,
+ {
+ perPage: MAX_ALLOWED_RESULTS,
+ filter: currentFilter,
+ excludedPolicies: [policyItem.id, 'all'],
+ },
+ searchableFields
+ );
+
+ const { data: allNotAssigned, isLoading: isLoadingAllNotAssigned } = useListArtifact(
+ apiClient,
+
+ {
+ excludedPolicies: [policyItem.id, 'all'],
+ },
+ searchableFields
+ );
+
+ const handleOnSearch = useCallback((query) => {
+ setSelectedArtifactIds([]);
+ setCurrentFilter(query);
+ }, []);
+
+ const handleOnConfirmAction = useCallback(() => {
+ if (!artifacts) {
+ return;
+ }
+ const artifactsToUpdate: ExceptionListItemSchema[] = [];
+ selectedArtifactIds.forEach((selectedId) => {
+ const artifact = artifacts.data.find((current) => current.id === selectedId);
+ if (artifact) {
+ artifact.tags = [...artifact.tags, `policy:${policyItem.id}`];
+ artifactsToUpdate.push(artifact);
+ }
+ });
+ bulkUpdateMutation.mutate(artifactsToUpdate);
+ }, [bulkUpdateMutation, artifacts, policyItem.id, selectedArtifactIds]);
+
+ const handleSelectArtifacts = (artifactId: string, selected: boolean) => {
+ setSelectedArtifactIds((currentSelectedArtifactIds) =>
+ selected
+ ? [...currentSelectedArtifactIds, artifactId]
+ : without([artifactId], currentSelectedArtifactIds)
+ );
+ };
+
+ const searchWarningMessage = useMemo(
+ () => (
+ <>
+
+ {labels.flyoutWarningCalloutMessage(MAX_ALLOWED_RESULTS)}
+
+
+ >
+ ),
+ [labels]
+ );
+
+ const assignableArtifacts = useMemo(
+ () => allNotAssigned?.total !== 0 && (artifacts?.total !== 0 || currentFilter !== ''),
+ [allNotAssigned?.total, artifacts?.total, currentFilter]
+ );
+
+ const isGlobalLoading = useMemo(
+ () => isLoadingArtifacts || isRefetchingArtifacts || isLoadingAllNotAssigned,
+ [isLoadingAllNotAssigned, isLoadingArtifacts, isRefetchingArtifacts]
+ );
+
+ const noItemsMessage = useMemo(() => {
+ if (isGlobalLoading) {
+ return null;
+ }
+
+ // there are no artifacts assignable to this policy
+ if (!assignableArtifacts) {
+ return (
+ {labels.flyoutNoArtifactsToBeAssignedMessage}
}
+ />
+ );
+ }
+
+ // there are no results for the current search
+ if (artifacts?.total === 0) {
+ return (
+ {labels.flyoutNoSearchResultsMessage}}
+ />
+ );
+ }
+ }, [
+ isGlobalLoading,
+ assignableArtifacts,
+ artifacts?.total,
+ labels.flyoutNoArtifactsToBeAssignedMessage,
+ labels.flyoutNoSearchResultsMessage,
+ ]);
+
+ return (
+
+
+
+ {labels.flyoutTitle}
+
+
+ {labels.flyoutSubtitle(policyItem.name)}
+
+
+ {(artifacts?.total || 0) > MAX_ALLOWED_RESULTS ? searchWarningMessage : null}
+ {!isLoadingAllNotAssigned && assignableArtifacts && (
+
+ )}
+
+
+
+
+ {noItemsMessage}
+
+
+
+
+
+ {labels.flyoutCancelButtonTitle}
+
+
+
+
+ {labels.flyoutSubmitButtonTitle(policyItem.name)}
+
+
+
+
+
+ );
+ }
+);
+
+PolicyArtifactsFlyout.displayName = 'PolicyArtifactsFlyout';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/translations.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/translations.ts
new file mode 100644
index 0000000000000..071a6f7334fb7
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/flyout/translations.ts
@@ -0,0 +1,95 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
+
+export const POLICY_ARTIFACT_FLYOUT_LABELS = Object.freeze({
+ flyoutWarningCalloutTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.searchWarning.title',
+ {
+ defaultMessage: 'Limited search results',
+ }
+ ),
+ flyoutWarningCalloutMessage: (maxNumber: number) =>
+ i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.searchWarning.text',
+ {
+ defaultMessage:
+ 'Only the first {maxNumber} artifacts are displayed. Please use the search bar to refine the results.',
+ values: { maxNumber },
+ }
+ ),
+ flyoutNoArtifactsToBeAssignedMessage: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.noAssignable',
+ {
+ defaultMessage: 'There are no artifacts that can be assigned to this policy.',
+ }
+ ),
+ flyoutNoSearchResultsMessage: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.noResults',
+ {
+ defaultMessage: 'No items found',
+ }
+ ),
+ flyoutTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.title',
+ {
+ defaultMessage: 'Assign artifacts',
+ }
+ ),
+ flyoutSubtitle: (policyName: string): string =>
+ i18n.translate('xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.subtitle', {
+ defaultMessage: 'Select artifacts to add to {policyName}',
+ values: { policyName },
+ }),
+ flyoutSearchPlaceholder: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.search.label',
+ {
+ defaultMessage: 'Search artifacts',
+ }
+ ),
+ flyoutCancelButtonTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.cancel',
+ {
+ defaultMessage: 'Cancel',
+ }
+ ),
+ flyoutSubmitButtonTitle: (policyName: string): string =>
+ i18n.translate('xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.confirm', {
+ defaultMessage: 'Assign to {policyName}',
+ values: { policyName },
+ }),
+ flyoutErrorMessage: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.toastError.text',
+ {
+ defaultMessage: `An error occurred updating artifacts`,
+ }
+ ),
+ flyoutSuccessMessageTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.toastSuccess.title',
+ {
+ defaultMessage: 'Success',
+ }
+ ),
+ flyoutSuccessMessageText: (updatedExceptions: ExceptionListItemSchema[]): string =>
+ updatedExceptions.length > 1
+ ? i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.toastSuccess.textMultiples',
+ {
+ defaultMessage: '{count} artifacts have been added to your list.',
+ values: { count: updatedExceptions.length },
+ }
+ )
+ : i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.flyout.toastSuccess.textSingle',
+ {
+ defaultMessage: '"{name}" has been added to your artifacts list.',
+ values: { name: updatedExceptions[0].name },
+ }
+ ),
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/index.ts
similarity index 65%
rename from x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/index.ts
rename to x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/index.ts
index ae6861787044d..77db9c5c4fa50 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/flyout/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/index.ts
@@ -5,4 +5,5 @@
* 2.0.
*/
-export { PolicyEventFiltersFlyout } from './policy_event_filters_flyout';
+export { PolicyArtifactsLayout } from './policy_artifacts_layout';
+export { POLICY_ARTIFACT_LAYOUT_LABELS } from './translations';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.test.tsx
new file mode 100644
index 0000000000000..62342b4e64fcd
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.test.tsx
@@ -0,0 +1,191 @@
+/*
+ * 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 { act, waitFor } from '@testing-library/react';
+import {
+ AppContextTestRender,
+ createAppRootMockRenderer,
+} from '../../../../../../common/mock/endpoint';
+import {
+ getEventFiltersListPath,
+ getPolicyDetailsArtifactsListPath,
+ getPolicyEventFiltersPath,
+} from '../../../../../common/routing';
+import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data';
+
+import { PolicyArtifactsLayout } from './policy_artifacts_layout';
+import { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types';
+import { parsePoliciesAndFilterToKql } from '../../../../../common/utils';
+import { eventFiltersListQueryHttpMock } from '../../../../event_filters/test_utils';
+import { getFoundExceptionListItemSchemaMock } from '../../../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock';
+import { getEndpointPrivilegesInitialStateMock } from '../../../../../../common/components/user_privileges/endpoint/mocks';
+import { POLICY_ARTIFACT_EVENT_FILTERS_LABELS } from '../../tabs/event_filters_translations';
+import { EventFiltersApiClient } from '../../../../event_filters/service/event_filters_api_client';
+import { SEARCHABLE_FIELDS as EVENT_FILTERS_SEARCHABLE_FIELDS } from '../../../../event_filters/constants';
+import { FormattedMessage } from '@kbn/i18n-react';
+
+let render: (externalPrivileges?: boolean) => Promise>;
+let mockedContext: AppContextTestRender;
+let renderResult: ReturnType;
+let policyItem: ImmutableObject;
+const generator = new EndpointDocGenerator();
+let mockedApi: ReturnType;
+let history: AppContextTestRender['history'];
+
+const getEventFiltersLabels = () => ({
+ ...POLICY_ARTIFACT_EVENT_FILTERS_LABELS,
+ layoutAboutMessage: (count: number, link: React.ReactElement): React.ReactNode => (
+
+ ),
+});
+
+describe('Policy artifacts layout', () => {
+ beforeEach(() => {
+ mockedContext = createAppRootMockRenderer();
+ mockedApi = eventFiltersListQueryHttpMock(mockedContext.coreStart.http);
+ mockedApi.responseProvider.eventFiltersList.mockClear();
+ policyItem = generator.generatePolicyPackagePolicy();
+ ({ history } = mockedContext);
+
+ getEndpointPrivilegesInitialStateMock({
+ canCreateArtifactsByPolicy: true,
+ });
+ render = async (externalPrivileges = true) => {
+ await act(async () => {
+ renderResult = mockedContext.render(
+
+ EventFiltersApiClient.getInstance(mockedContext.coreStart.http)
+ }
+ searchableFields={EVENT_FILTERS_SEARCHABLE_FIELDS}
+ getArtifactPath={getEventFiltersListPath}
+ getPolicyArtifactsPath={getPolicyEventFiltersPath}
+ externalPrivileges={externalPrivileges}
+ />
+ );
+ await waitFor(mockedApi.responseProvider.eventFiltersList);
+ });
+ return renderResult;
+ };
+ history.push(getPolicyEventFiltersPath(policyItem.id));
+ });
+
+ it('should render layout with a loader', async () => {
+ const component = mockedContext.render(
+
+ EventFiltersApiClient.getInstance(mockedContext.coreStart.http)
+ }
+ searchableFields={[...EVENT_FILTERS_SEARCHABLE_FIELDS]}
+ getArtifactPath={getEventFiltersListPath}
+ getPolicyArtifactsPath={getPolicyEventFiltersPath}
+ />
+ );
+ expect(component.getByTestId('policy-artifacts-loading-spinner')).toBeTruthy();
+ });
+
+ it('should render layout with no assigned artifacts data when there are no artifacts', async () => {
+ mockedApi.responseProvider.eventFiltersList.mockReturnValue(
+ getFoundExceptionListItemSchemaMock(0)
+ );
+
+ await render();
+ expect(await renderResult.findByTestId('policy-artifacts-empty-unexisting')).not.toBeNull();
+ });
+
+ it('should render layout with no assigned artifacts data when there are artifacts', async () => {
+ mockedApi.responseProvider.eventFiltersList.mockImplementation(
+ (args?: { query: { filter: string } }) => {
+ if (
+ !args ||
+ args.query.filter !== parsePoliciesAndFilterToKql({ policies: [policyItem.id, 'all'] })
+ ) {
+ return getFoundExceptionListItemSchemaMock(1);
+ } else {
+ return getFoundExceptionListItemSchemaMock(0);
+ }
+ }
+ );
+
+ await render();
+
+ expect(await renderResult.findByTestId('policy-artifacts-empty-unassigned')).not.toBeNull();
+ });
+
+ it('should render layout with data', async () => {
+ mockedApi.responseProvider.eventFiltersList.mockReturnValue(
+ getFoundExceptionListItemSchemaMock(3)
+ );
+ await render();
+ expect(await renderResult.findByTestId('policy-artifacts-header-section')).not.toBeNull();
+ expect(await renderResult.findByTestId('policy-artifacts-layout-about')).not.toBeNull();
+ expect((await renderResult.findByTestId('policy-artifacts-layout-about')).textContent).toMatch(
+ '3 event filters'
+ );
+ });
+
+ it('should hide `Assign artifacts to policy` on empty state with unassigned policies when downgraded to a gold or below license', async () => {
+ getEndpointPrivilegesInitialStateMock({
+ canCreateArtifactsByPolicy: false,
+ });
+ mockedApi.responseProvider.eventFiltersList.mockReturnValue(
+ getFoundExceptionListItemSchemaMock(0)
+ );
+
+ await render();
+ mockedContext.history.push(getPolicyDetailsArtifactsListPath(policyItem.id));
+ expect(renderResult.queryByTestId('artifacts-assign-button')).toBeNull();
+ });
+
+ it('should hide the `Assign artifacts to policy` button license is downgraded to gold or below', async () => {
+ getEndpointPrivilegesInitialStateMock({
+ canCreateArtifactsByPolicy: false,
+ });
+ mockedApi.responseProvider.eventFiltersList.mockReturnValue(
+ getFoundExceptionListItemSchemaMock(5)
+ );
+
+ await render();
+ mockedContext.history.push(getPolicyDetailsArtifactsListPath(policyItem.id));
+
+ expect(renderResult.queryByTestId('artifacts-assign-button')).toBeNull();
+ });
+
+ it('should hide the `Assign artifacts` flyout when license is downgraded to gold or below', async () => {
+ getEndpointPrivilegesInitialStateMock({
+ canCreateArtifactsByPolicy: false,
+ });
+ mockedApi.responseProvider.eventFiltersList.mockReturnValue(
+ getFoundExceptionListItemSchemaMock(2)
+ );
+
+ await render();
+ mockedContext.history.push(
+ `${getPolicyDetailsArtifactsListPath(policyItem.id)}/eventFilters?show=list`
+ );
+
+ expect(renderResult.queryByTestId('artifacts-assign-flyout')).toBeNull();
+ });
+
+ describe('Without external privileges', () => {
+ it('should not display the assign policies button', async () => {
+ mockedApi.responseProvider.eventFiltersList.mockReturnValue(
+ getFoundExceptionListItemSchemaMock(5)
+ );
+ await render(false);
+ expect(renderResult.queryByTestId('artifacts-assign-button')).toBeNull();
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx
new file mode 100644
index 0000000000000..c1f771fdcccde
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/policy_artifacts_layout.tsx
@@ -0,0 +1,247 @@
+/*
+ * 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, useCallback, useState } from 'react';
+import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
+import {
+ EuiTitle,
+ EuiPageHeader,
+ EuiPageHeaderSection,
+ EuiText,
+ EuiSpacer,
+ EuiLink,
+ EuiButton,
+ EuiPageContent,
+} from '@elastic/eui';
+import { useAppUrl } from '../../../../../../common/lib/kibana';
+import { APP_UI_ID } from '../../../../../../../common/constants';
+import { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types';
+import { ManagementPageLoader } from '../../../../../components/management_page_loader';
+import { useUrlParams } from '../../../../../components/hooks/use_url_params';
+import { useUserPrivileges } from '../../../../../../common/components/user_privileges';
+import { usePolicyDetailsArtifactsNavigateCallback } from '../../policy_hooks';
+import { ExceptionsListApiClient } from '../../../../../services/exceptions_list/exceptions_list_api_client';
+import { useListArtifact } from '../../../../../hooks/artifacts';
+import { PolicyArtifactsEmptyUnassigned, PolicyArtifactsEmptyUnexisting } from '../empty';
+import { PolicyArtifactsList } from '../list';
+import { PolicyArtifactsFlyout } from '../flyout';
+import { PolicyArtifactsPageLabels, policyArtifactsPageLabels } from '../translations';
+import { PolicyArtifactsDeleteModal } from '../delete_modal';
+import { EventFiltersPageLocation } from '../../../../event_filters/types';
+import { HostIsolationExceptionsPageLocation } from '../../../../host_isolation_exceptions/types';
+import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state';
+
+interface PolicyArtifactsLayoutProps {
+ policyItem?: ImmutableObject | undefined;
+ /** A list of labels for the given policy artifact page. Not all have to be defined, only those that should override the defaults */
+ labels: PolicyArtifactsPageLabels;
+ getExceptionsListApiClient: () => ExceptionsListApiClient;
+ searchableFields: readonly string[];
+ getArtifactPath: (
+ location?:
+ | Partial
+ | Partial
+ | Partial
+ ) => string;
+ getPolicyArtifactsPath: (policyId: string) => string;
+ /** A boolean to check extra privileges for restricted actions, true when it's allowed, false when not */
+ externalPrivileges?: boolean;
+}
+export const PolicyArtifactsLayout = React.memo(
+ ({
+ policyItem,
+ labels: _labels = {},
+ getExceptionsListApiClient,
+ searchableFields,
+ getArtifactPath,
+ getPolicyArtifactsPath,
+ externalPrivileges = true,
+ }) => {
+ const exceptionsListApiClient = useMemo(
+ () => getExceptionsListApiClient(),
+ [getExceptionsListApiClient]
+ );
+ const { getAppUrl } = useAppUrl();
+ const navigateCallback = usePolicyDetailsArtifactsNavigateCallback(
+ exceptionsListApiClient.listId
+ );
+ const { canCreateArtifactsByPolicy } = useUserPrivileges().endpointPrivileges;
+ const { urlParams } = useUrlParams();
+ const [exceptionItemToDelete, setExceptionItemToDelete] = useState<
+ ExceptionListItemSchema | undefined
+ >();
+
+ const labels = useMemo(() => {
+ return {
+ ...policyArtifactsPageLabels,
+ ..._labels,
+ };
+ }, [_labels]);
+
+ const { data: allAssigned, isLoading: isLoadingAllAssigned } = useListArtifact(
+ exceptionsListApiClient,
+ {
+ policies: policyItem ? [policyItem.id, 'all'] : [],
+ },
+ searchableFields
+ );
+
+ const {
+ data: allArtifacts,
+ isLoading: isLoadingAllArtifacts,
+ isRefetching: isRefetchingAllArtifacts,
+ } = useListArtifact(exceptionsListApiClient, {}, searchableFields, {}, ['allExisting']);
+
+ const handleOnClickAssignButton = useCallback(() => {
+ navigateCallback({ show: 'list' });
+ }, [navigateCallback]);
+ const handleOnCloseFlyout = useCallback(() => {
+ navigateCallback({ show: undefined });
+ }, [navigateCallback]);
+
+ const handleDeleteModalClose = useCallback(() => {
+ setExceptionItemToDelete(undefined);
+ }, [setExceptionItemToDelete]);
+
+ const handleOnDeleteActionCallback = useCallback(
+ (item) => {
+ setExceptionItemToDelete(item);
+ },
+ [setExceptionItemToDelete]
+ );
+
+ const assignToPolicyButton = useMemo(
+ () => (
+
+ {labels.layoutAssignButtonTitle}
+
+ ),
+ [handleOnClickAssignButton, labels.layoutAssignButtonTitle]
+ );
+
+ const aboutInfo = useMemo(() => {
+ const link = (
+
+ {labels.layoutViewAllLinkMessage}
+
+ );
+
+ return labels.layoutAboutMessage(allAssigned?.total || 0, link);
+ }, [getAppUrl, getArtifactPath, labels, allAssigned?.total]);
+
+ const isGlobalLoading = useMemo(
+ () => isLoadingAllAssigned || isLoadingAllArtifacts || isRefetchingAllArtifacts,
+ [isLoadingAllAssigned, isLoadingAllArtifacts, isRefetchingAllArtifacts]
+ );
+
+ const isEmptyState = useMemo(() => allAssigned && allAssigned.total === 0, [allAssigned]);
+
+ if (!policyItem || isGlobalLoading) {
+ return ;
+ }
+
+ if (isEmptyState) {
+ return (
+ <>
+ {canCreateArtifactsByPolicy && urlParams.show === 'list' && (
+
+ )}
+ {allArtifacts && allArtifacts.total !== 0 ? (
+
+ ) : (
+
+ )}
+ >
+ );
+ }
+
+ return (
+
+
+
+
+ {labels.layoutTitle}
+
+
+
+
+
+ {aboutInfo}
+
+
+
+ {canCreateArtifactsByPolicy && externalPrivileges && assignToPolicyButton}
+
+
+ {canCreateArtifactsByPolicy && externalPrivileges && urlParams.show === 'list' && (
+
+ )}
+ {exceptionItemToDelete && (
+
+ )}
+
+
+
+
+
+ );
+ }
+);
+
+PolicyArtifactsLayout.displayName = 'PolicyArtifactsLayout';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/translations.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/translations.ts
new file mode 100644
index 0000000000000..82ffb8b16ca7a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/layout/translations.ts
@@ -0,0 +1,34 @@
+/*
+ * 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';
+
+export const POLICY_ARTIFACT_LAYOUT_LABELS = Object.freeze({
+ layoutTitle: i18n.translate('xpack.securitySolution.endpoint.policy.artifacts.layout.title', {
+ defaultMessage: 'Assigned artifacts',
+ }),
+ layoutAssignButtonTitle: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.assignToPolicy',
+ {
+ defaultMessage: 'Assign artifact to policy',
+ }
+ ),
+ layoutViewAllLinkMessage: i18n.translate(
+ 'xpack.securitySolution.endpoint.policy.artifacts.layout.about.viewAllLinkLabel',
+ {
+ defaultMessage: 'view all artifacts',
+ }
+ ),
+ layoutAboutMessage: (count: number, _: React.ReactElement): React.ReactNode => {
+ return i18n.translate('xpack.securitySolution.endpoint.policy.artifacts.layout.about', {
+ defaultMessage:
+ 'There {count, plural, one {is} other {are}} {count} {count, plural, =1 {artifact} other {artifacts}} associated with this policy. Click here to view all artifacts',
+ values: { count },
+ });
+ },
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/index.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/index.ts
similarity index 67%
rename from x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/index.ts
rename to x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/index.ts
index 4dd64e5c2f938..260b580de7497 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/delete_modal/index.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/index.ts
@@ -5,4 +5,5 @@
* 2.0.
*/
-export { PolicyEventFiltersDeleteModal } from './policy_event_filters_delete_modal';
+export { PolicyArtifactsList } from './policy_artifacts_list';
+export { POLICY_ARTIFACT_LIST_LABELS } from './translations';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx
similarity index 64%
rename from x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.test.tsx
rename to x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx
index e8425a57b4012..9ce3d56dac472 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/event_filters/list/policy_event_filters_list.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.test.tsx
@@ -15,12 +15,14 @@ import {
} from '../../../../../../common/mock/endpoint';
import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data';
import { PolicyData } from '../../../../../../../common/endpoint/types';
-import { getPolicyEventFiltersPath } from '../../../../../common/routing';
+import { getEventFiltersListPath, getPolicyEventFiltersPath } from '../../../../../common/routing';
import { eventFiltersListQueryHttpMock } from '../../../../event_filters/test_utils';
-import { PolicyEventFiltersList } from './policy_event_filters_list';
+import { PolicyArtifactsList } from './policy_artifacts_list';
import { parseQueryFilterToKQL, parsePoliciesAndFilterToKql } from '../../../../../common/utils';
import { SEARCHABLE_FIELDS } from '../../../../event_filters/constants';
import { getEndpointPrivilegesInitialStateMock } from '../../../../../../common/components/user_privileges/endpoint/mocks';
+import { POLICY_ARTIFACT_LIST_LABELS } from './translations';
+import { EventFiltersApiClient } from '../../../../event_filters/service/event_filters_api_client';
const endpointGenerator = new EndpointDocGenerator('seed');
const getDefaultQueryParameters = (customFilter: string | undefined = '') => ({
@@ -36,22 +38,37 @@ const getDefaultQueryParameters = (customFilter: string | undefined = '') => ({
},
});
-describe('Policy details event filters list', () => {
- let render: () => Promise>;
+describe('Policy details artifacts list', () => {
+ let render: (externalPrivileges?: boolean) => Promise>;
let renderResult: ReturnType;
let history: AppContextTestRender['history'];
let mockedContext: AppContextTestRender;
let mockedApi: ReturnType;
let policy: PolicyData;
-
+ let handleOnDeleteActionCallbackMock: jest.Mock;
beforeEach(() => {
policy = endpointGenerator.generatePolicyPackagePolicy();
mockedContext = createAppRootMockRenderer();
mockedApi = eventFiltersListQueryHttpMock(mockedContext.coreStart.http);
({ history } = mockedContext);
- render = async () => {
+ handleOnDeleteActionCallbackMock = jest.fn();
+ getEndpointPrivilegesInitialStateMock({
+ canCreateArtifactsByPolicy: true,
+ });
+ render = async (externalPrivileges = true) => {
await act(async () => {
- renderResult = mockedContext.render();
+ renderResult = mockedContext.render(
+
+ );
await waitFor(mockedApi.responseProvider.eventFiltersList);
});
return renderResult;
@@ -65,8 +82,8 @@ describe('Policy details event filters list', () => {
getFoundExceptionListItemSchemaMock(0)
);
await render();
- expect(renderResult.getByTestId('policyDetailsEventFiltersSearchCount')).toHaveTextContent(
- 'Showing 0 event filters'
+ expect(renderResult.getByTestId('policyDetailsArtifactsSearchCount')).toHaveTextContent(
+ 'Showing 0 artifacts'
);
expect(renderResult.getByTestId('searchField')).toBeTruthy();
});
@@ -76,22 +93,22 @@ describe('Policy details event filters list', () => {
getFoundExceptionListItemSchemaMock(3)
);
await render();
- expect(renderResult.getAllByTestId('eventFilters-collapsed-list-card')).toHaveLength(3);
+ expect(renderResult.getAllByTestId('artifacts-collapsed-list-card')).toHaveLength(3);
expect(
- renderResult.queryAllByTestId('eventFilters-collapsed-list-card-criteriaConditions')
+ renderResult.queryAllByTestId('artifacts-collapsed-list-card-criteriaConditions')
).toHaveLength(0);
});
it('should expand an item when expand is clicked', async () => {
await render();
- expect(renderResult.getAllByTestId('eventFilters-collapsed-list-card')).toHaveLength(1);
+ expect(renderResult.getAllByTestId('artifacts-collapsed-list-card')).toHaveLength(1);
userEvent.click(
- renderResult.getByTestId('eventFilters-collapsed-list-card-header-expandCollapse')
+ renderResult.getByTestId('artifacts-collapsed-list-card-header-expandCollapse')
);
expect(
- renderResult.queryAllByTestId('eventFilters-collapsed-list-card-criteriaConditions')
+ renderResult.queryAllByTestId('artifacts-collapsed-list-card-criteriaConditions')
).toHaveLength(1);
});
@@ -130,7 +147,7 @@ describe('Policy details event filters list', () => {
await render();
// click the actions button
userEvent.click(
- renderResult.getByTestId('eventFilters-collapsed-list-card-header-actions-button')
+ renderResult.getByTestId('artifacts-collapsed-list-card-header-actions-button')
);
expect(renderResult.queryByTestId('view-full-details-action')).toBeTruthy();
});
@@ -144,9 +161,24 @@ describe('Policy details event filters list', () => {
);
await render();
userEvent.click(
- renderResult.getByTestId('eventFilters-collapsed-list-card-header-actions-button')
+ renderResult.getByTestId('artifacts-collapsed-list-card-header-actions-button')
);
expect(renderResult.queryByTestId('remove-from-policy-action')).toBeNull();
});
+
+ describe('without external privileges', () => {
+ it('should not display the delete action, do show the full details', async () => {
+ mockedApi.responseProvider.eventFiltersList.mockReturnValue(
+ getFoundExceptionListItemSchemaMock(1)
+ );
+ await render(false);
+ // click the actions button
+ userEvent.click(
+ await renderResult.findByTestId('artifacts-collapsed-list-card-header-actions-button')
+ );
+ expect(renderResult.queryByTestId('remove-from-policy-action')).toBeFalsy();
+ expect(renderResult.queryByTestId('view-full-details-action')).toBeTruthy();
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx
new file mode 100644
index 0000000000000..4c87ba883f39a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/artifacts/list/policy_artifacts_list.tsx
@@ -0,0 +1,207 @@
+/*
+ * 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, { useCallback, useMemo, useState } from 'react';
+import { EuiSpacer, EuiText, Pagination } from '@elastic/eui';
+import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
+import { useAppUrl } from '../../../../../../common/lib/kibana';
+import { APP_UI_ID } from '../../../../../../../common/constants';
+import { SearchExceptions } from '../../../../../components/search_exceptions';
+import { useEndpointPoliciesToArtifactPolicies } from '../../../../../components/artifact_entry_card/hooks/use_endpoint_policies_to_artifact_policies';
+import { useUrlParams } from '../../../../../components/hooks/use_url_params';
+import { useUrlPagination } from '../../../../../components/hooks/use_url_pagination';
+import { useGetEndpointSpecificPolicies } from '../../../../../services/policies/hooks';
+import {
+ ArtifactCardGrid,
+ ArtifactCardGridProps,
+} from '../../../../../components/artifact_card_grid';
+import { usePolicyDetailsArtifactsNavigateCallback } from '../../policy_hooks';
+import { ImmutableObject, PolicyData } from '../../../../../../../common/endpoint/types';
+import { isGlobalPolicyEffected } from '../../../../../components/effected_policy_select/utils';
+import { useUserPrivileges } from '../../../../../../common/components/user_privileges';
+import { useGetLinkTo } from '../empty/use_policy_artifacts_empty_hooks';
+import { ExceptionsListApiClient } from '../../../../../services/exceptions_list/exceptions_list_api_client';
+import { useListArtifact } from '../../../../../hooks/artifacts';
+import { POLICY_ARTIFACT_LIST_LABELS } from './translations';
+import { EventFiltersPageLocation } from '../../../../event_filters/types';
+import { TrustedAppsListPageLocation } from '../../../../trusted_apps/state';
+import { HostIsolationExceptionsPageLocation } from '../../../../host_isolation_exceptions/types';
+
+interface PolicyArtifactsListProps {
+ policy: ImmutableObject;
+ apiClient: ExceptionsListApiClient;
+ searchableFields: string[];
+ getArtifactPath: (
+ location?:
+ | Partial
+ | Partial
+ | Partial
+ ) => string;
+ getPolicyArtifactsPath: (policyId: string) => string;
+ labels: typeof POLICY_ARTIFACT_LIST_LABELS;
+ onDeleteActionCallback: (item: ExceptionListItemSchema) => void;
+ externalPrivileges?: boolean;
+}
+
+export const PolicyArtifactsList = React.memo(
+ ({
+ policy,
+ apiClient,
+ searchableFields,
+ getArtifactPath,
+ getPolicyArtifactsPath,
+ labels,
+ onDeleteActionCallback,
+ externalPrivileges = true,
+ }) => {
+ const { getAppUrl } = useAppUrl();
+ const { canCreateArtifactsByPolicy } = useUserPrivileges().endpointPrivileges;
+ const policiesRequest = useGetEndpointSpecificPolicies({ perPage: 1000 });
+ const navigateCallback = usePolicyDetailsArtifactsNavigateCallback(apiClient.listId);
+ const { urlParams } = useUrlParams();
+ const [expandedItemsMap, setExpandedItemsMap] = useState