alert._id),
+ },
+ },
+ aggs: {
+ uuids: {
+ terms: {
+ field: ALERT_UUID,
+ size: CHUNK_SIZE,
+ },
+ },
+ },
+ size: 0,
+ },
+ };
+ const response = await ruleDataClient
+ .getReader({ namespace: options.spaceId })
+ .search(request);
+ const uuidsMap: Record = {};
+ const aggs = response.aggregations as
+ | Record }>
+ | undefined;
+ if (aggs != null) {
+ aggs.uuids.buckets.forEach((bucket) => (uuidsMap[bucket.key] = true));
+ const newAlerts = alertChunk.filter((alert) => !uuidsMap[alert._id]);
+ filteredAlerts.push(...newAlerts);
+ } else {
+ filteredAlerts.push(...alertChunk);
+ }
+ }
+
+ if (filteredAlerts.length === 0) {
+ return { createdAlerts: [] };
+ }
+
+ const augmentedAlerts = filteredAlerts.map((alert) => {
+ return {
+ ...alert,
+ _source: {
+ [VERSION]: ruleDataClient.kibanaVersion,
+ ...commonRuleFields,
+ ...alert._source,
+ },
+ };
+ });
+
const response = await ruleDataClient
.getWriter({ namespace: options.spaceId })
.bulk({
- body: alerts.flatMap((alert) => [
- { index: { _id: alert.id } },
- {
- [VERSION]: ruleDataClient.kibanaVersion,
- ...commonRuleFields,
- ...alert.fields,
- },
+ body: augmentedAlerts.flatMap((alert) => [
+ { create: { _id: alert._id } },
+ alert._source,
]),
refresh,
});
- return response;
+
+ if (response == null) {
+ return { createdAlerts: [] };
+ }
+
+ return {
+ createdAlerts: augmentedAlerts.map((alert, idx) => {
+ const responseItem = response.body.items[idx].create;
+ return {
+ _id: responseItem?._id ?? '',
+ _index: responseItem?._index ?? '',
+ ...alert._source,
+ };
+ }),
+ };
} else {
logger.debug('Writing is disabled.');
+ return { createdAlerts: [] };
}
},
},
diff --git a/x-pack/plugins/rule_registry/server/utils/persistence_types.ts b/x-pack/plugins/rule_registry/server/utils/persistence_types.ts
index 326a8bef49abd..5541bc6a6d00e 100644
--- a/x-pack/plugins/rule_registry/server/utils/persistence_types.ts
+++ b/x-pack/plugins/rule_registry/server/utils/persistence_types.ts
@@ -5,8 +5,6 @@
* 2.0.
*/
-import type { TransportResult } from '@elastic/elasticsearch';
-import { BulkResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { Logger } from '@kbn/logging';
import {
AlertExecutorOptions,
@@ -19,13 +17,17 @@ import {
import { WithoutReservedActionGroups } from '../../../alerting/common';
import { IRuleDataClient } from '../rule_data_client';
-export type PersistenceAlertService = (
+export type PersistenceAlertService = (
alerts: Array<{
- id: string;
- fields: Record;
+ _id: string;
+ _source: T;
}>,
refresh: boolean | 'wait_for'
-) => Promise | undefined>;
+) => Promise>;
+
+export interface PersistenceAlertServiceResult {
+ createdAlerts: Array;
+}
export interface PersistenceServices {
alertWithPersistence: PersistenceAlertService;
diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx
index a4843e4637d8b..f950167d4b82e 100644
--- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx
+++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx
@@ -528,7 +528,7 @@ export class APIKeysGridPage extends Component {
render: (creation: string, item: ApiKey) => (
{item.id === createdApiKey?.id ? (
-
+
({
import { act } from '@testing-library/react';
-import { coreMock, scopedHistoryMock } from 'src/core/public/mocks';
+import { coreMock, scopedHistoryMock, themeServiceMock } from 'src/core/public/mocks';
import type { Unmount } from 'src/plugins/management/public/types';
import { securityMock } from '../../mocks';
@@ -52,6 +52,7 @@ describe('apiKeysManagementApp', () => {
element: container,
setBreadcrumbs,
history: scopedHistoryMock.create(),
+ theme$: themeServiceMock.createTheme$(),
});
});
diff --git a/x-pack/plugins/security/public/management/badges/enabled_badge.tsx b/x-pack/plugins/security/public/management/badges/enabled_badge.tsx
index f55914997c72e..a03a5ab3060e3 100644
--- a/x-pack/plugins/security/public/management/badges/enabled_badge.tsx
+++ b/x-pack/plugins/security/public/management/badges/enabled_badge.tsx
@@ -21,7 +21,7 @@ interface Props {
export const EnabledBadge = (props: Props) => {
return (
-
+
diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx
index 86f7892e49d24..176d66049eade 100644
--- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx
+++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx
@@ -5,6 +5,7 @@
* 2.0.
*/
+import 'react-ace';
import 'brace/mode/json';
import 'brace/theme/github';
diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx
index 373f3366f36c8..c983f2c704f4a 100644
--- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx
+++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.tsx
@@ -255,7 +255,7 @@ export class RoleMappingsGridPage extends Component {
) : undefined,
toolsRight: (
this.reloadRoleMappings()}
data-test-subj="reloadButton"
diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx
index 892c7940675d3..3b7e96ffabd1e 100644
--- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx
+++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_management_app.test.tsx
@@ -8,7 +8,7 @@
import { act } from '@testing-library/react';
import { noop } from 'lodash';
-import { coreMock, scopedHistoryMock } from 'src/core/public/mocks';
+import { coreMock, scopedHistoryMock, themeServiceMock } from 'src/core/public/mocks';
import type { Unmount } from 'src/plugins/management/public/types';
import { roleMappingsManagementApp } from './role_mappings_management_app';
@@ -46,6 +46,7 @@ async function mountApp(basePath: string, pathname: string) {
element: container,
setBreadcrumbs,
history: scopedHistoryMock.create({ pathname }),
+ theme$: themeServiceMock.createTheme$(),
});
});
diff --git a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx
index faab47a858d67..007c3e306372e 100644
--- a/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx
+++ b/x-pack/plugins/security/public/management/roles/roles_management_app.test.tsx
@@ -8,7 +8,7 @@
import { act } from '@testing-library/react';
import { noop } from 'lodash';
-import { coreMock, scopedHistoryMock } from 'src/core/public/mocks';
+import { coreMock, scopedHistoryMock, themeServiceMock } from 'src/core/public/mocks';
import type { Unmount } from 'src/plugins/management/public/types';
import { featuresPluginMock } from '../../../../features/public/mocks';
@@ -48,6 +48,7 @@ async function mountApp(basePath: string, pathname: string) {
element: container,
setBreadcrumbs,
history: scopedHistoryMock.create({ pathname }),
+ theme$: themeServiceMock.createTheme$(),
});
});
diff --git a/x-pack/plugins/security/public/management/users/users_management_app.test.tsx b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx
index f25fb211cb9de..84a6e82bf12ae 100644
--- a/x-pack/plugins/security/public/management/users/users_management_app.test.tsx
+++ b/x-pack/plugins/security/public/management/users/users_management_app.test.tsx
@@ -8,7 +8,7 @@
import { act } from '@testing-library/react';
import { noop } from 'lodash';
-import { coreMock, scopedHistoryMock } from 'src/core/public/mocks';
+import { coreMock, scopedHistoryMock, themeServiceMock } from 'src/core/public/mocks';
import type { Unmount } from 'src/plugins/management/public/types';
import { securityMock } from '../../mocks';
@@ -33,6 +33,7 @@ describe('usersManagementApp', () => {
element,
setBreadcrumbs,
history,
+ theme$: themeServiceMock.createTheme$(),
});
});
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts
index 2fee3e4c39d1d..2dc4f49919ef7 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts
@@ -177,7 +177,7 @@ export interface ResolverPaginatedEvents {
}
/**
- * Returned by the server via /api/endpoint/metadata
+ * Returned by the server via POST /api/endpoint/metadata
*/
export interface HostResultList {
/* the hosts restricted by the page size */
@@ -1231,3 +1231,22 @@ export interface ListPageRouteState {
/** The label for the button */
backButtonLabel?: string;
}
+
+/**
+ * REST API standard base response for list types
+ */
+export interface BaseListResponse {
+ data: unknown[];
+ page: number;
+ pageSize: number;
+ total: number;
+ sort?: string;
+ sortOrder?: 'asc' | 'desc';
+}
+
+/**
+ * Returned by the server via GET /api/endpoint/metadata
+ */
+export interface MetadataListResponse extends BaseListResponse {
+ data: HostInfo[];
+}
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
index 378de8f0bc593..ef6db14dba896 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts
@@ -40,6 +40,7 @@ import {
INDICATOR_INDEX_PATTERNS,
INDICATOR_INDEX_QUERY,
INDICATOR_MAPPING,
+ INDICATOR_PREFIX_OVERRIDE,
INVESTIGATION_NOTES_MARKDOWN,
INVESTIGATION_NOTES_TOGGLE,
MITRE_ATTACK_DETAILS,
@@ -448,6 +449,10 @@ describe('indicator match', () => {
cy.get(ABOUT_DETAILS).within(() => {
getDetails(SEVERITY_DETAILS).should('have.text', getNewThreatIndicatorRule().severity);
getDetails(RISK_SCORE_DETAILS).should('have.text', getNewThreatIndicatorRule().riskScore);
+ getDetails(INDICATOR_PREFIX_OVERRIDE).should(
+ 'have.text',
+ getNewThreatIndicatorRule().threatIndicatorPath
+ );
getDetails(REFERENCE_URLS_DETAILS).should((details) => {
expect(removeExternalLinkText(details.text())).equal(expectedUrls);
});
diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts
index 0a9eecf83c7fc..1c81099d43dd5 100644
--- a/x-pack/plugins/security_solution/cypress/objects/rule.ts
+++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts
@@ -77,6 +77,7 @@ export interface ThreatIndicatorRule extends CustomRule {
indicatorIndexPattern: string[];
indicatorMappingField: string;
indicatorIndexField: string;
+ threatIndicatorPath: string;
type?: string;
atomic?: string;
}
@@ -405,6 +406,7 @@ export const getNewThreatIndicatorRule = (): ThreatIndicatorRule => ({
atomic: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
timeline: getIndicatorMatchTimelineTemplate(),
maxSignals: 100,
+ threatIndicatorPath: 'threat.indicator',
});
export const duplicatedRuleName = `${getNewThreatIndicatorRule().name} [Duplicate]`;
diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts
index fb1fded1fe8a6..cdad6096ece1f 100644
--- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts
@@ -64,6 +64,8 @@ export const RULE_NAME_OVERRIDE_DETAILS = 'Rule name override';
export const RISK_SCORE_DETAILS = 'Risk score';
+export const INDICATOR_PREFIX_OVERRIDE = 'Indicator prefix override';
+
export const RISK_SCORE_OVERRIDE_DETAILS = 'Risk score override';
export const REFERENCE_URLS_DETAILS = 'Reference URLs';
diff --git a/x-pack/plugins/security_solution/public/cases/components/callout/callout.test.tsx b/x-pack/plugins/security_solution/public/cases/components/callout/callout.test.tsx
index 0a0caa40a8783..01c581f5401ba 100644
--- a/x-pack/plugins/security_solution/public/cases/components/callout/callout.test.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/callout/callout.test.tsx
@@ -60,7 +60,7 @@ describe('Callout', () => {
const className =
wrapper.find(`button[data-test-subj="callout-dismiss-md5-hex"]`).first().prop('className') ??
'';
- expect(className.includes('euiButton--secondary')).toBeTruthy();
+ expect(className.includes('euiButton--success')).toBeTruthy();
});
it('transform the button color correctly - warning', () => {
diff --git a/x-pack/plugins/security_solution/public/cases/components/callout/callout.tsx b/x-pack/plugins/security_solution/public/cases/components/callout/callout.tsx
index f00fa84c6ff0a..d0493ec7acb9c 100644
--- a/x-pack/plugins/security_solution/public/cases/components/callout/callout.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/callout/callout.tsx
@@ -37,11 +37,7 @@ const CallOutComponent = ({
return showCallOut && !isEmpty(messages) ? (
-
+
{i18n.DISMISS_CALLOUT}
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout_dismiss_button.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout_dismiss_button.tsx
index c657ee243b74b..b00072be6cc5f 100644
--- a/x-pack/plugins/security_solution/public/common/components/callouts/callout_dismiss_button.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout_dismiss_button.tsx
@@ -23,7 +23,7 @@ export const CallOutDismissButton: FC = ({
onClick = noop,
}) => {
const { type } = message;
- const buttonColor = type === 'success' ? 'secondary' : type;
+ const buttonColor = type;
const buttonText = text ?? i18n.DISMISS_BUTTON;
const handleClick = useCallback(() => onClick(message), [onClick, message]);
diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/editable_title.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/editable_title.tsx
index 487e0fe4981f1..3744297ded56a 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_page/editable_title.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_page/editable_title.tsx
@@ -77,7 +77,7 @@ const EditableTitleComponent: React.FC = ({
- secondary
+ success
{
{'primary'}
-
- {'secondary'}
+
+ {'success'}
{'danger'}
diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.test.tsx
index 12264e28d40c9..7fce50284d0d9 100644
--- a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.test.tsx
@@ -60,8 +60,8 @@ describe('item_details_card', () => {
{'primary'}
-
- {'secondary'}
+
+ {'success'}
{'danger'}
diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
index 3440e5f4488c6..a2fffc32be46d 100644
--- a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
@@ -22,28 +22,36 @@ exports[`Paginated Table Component rendering it renders the default load more ta
Array [
Object {
"field": "node.host.name",
- "hideForMobile": false,
+ "mobileOptions": Object {
+ "show": true,
+ },
"name": "Host",
"render": [Function],
"truncateText": false,
},
Object {
"field": "node.host.firstSeen",
- "hideForMobile": false,
+ "mobileOptions": Object {
+ "show": true,
+ },
"name": "First seen",
"render": [Function],
"truncateText": false,
},
Object {
"field": "node.host.os",
- "hideForMobile": false,
+ "mobileOptions": Object {
+ "show": true,
+ },
"name": "OS",
"render": [Function],
"truncateText": false,
},
Object {
"field": "node.host.version",
- "hideForMobile": false,
+ "mobileOptions": Object {
+ "show": true,
+ },
"name": "Version",
"render": [Function],
"truncateText": false,
diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/index.mock.tsx b/x-pack/plugins/security_solution/public/common/components/paginated_table/index.mock.tsx
index 070c8a8d53b43..2d728ffa915fc 100644
--- a/x-pack/plugins/security_solution/public/common/components/paginated_table/index.mock.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/index.mock.tsx
@@ -57,28 +57,28 @@ export const getHostsColumns = (): [
field: 'node.host.name',
name: 'Host',
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: (name: string) => getOrEmptyTagFromValue(name),
},
{
field: 'node.host.firstSeen',
name: 'First seen',
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: (firstSeen: string) => getOrEmptyTagFromValue(firstSeen),
},
{
field: 'node.host.os',
name: 'OS',
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: (os: string) => getOrEmptyTagFromValue(os),
},
{
field: 'node.host.version',
name: 'Version',
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: (version: string) => getOrEmptyTagFromValue(version),
},
];
diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/index.tsx b/x-pack/plugins/security_solution/public/common/components/paginated_table/index.tsx
index f5828c9f65db9..ef49c8e2a5e8c 100644
--- a/x-pack/plugins/security_solution/public/common/components/paginated_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/index.tsx
@@ -17,6 +17,7 @@ import {
EuiLoadingContent,
EuiPagination,
EuiPopover,
+ EuiTableRowCellProps,
} from '@elastic/eui';
import { noop } from 'lodash/fp';
import React, { FC, memo, useState, useMemo, useEffect, ComponentType } from 'react';
@@ -126,8 +127,7 @@ type Func = (arg: T) => string | number;
export interface Columns {
align?: string;
field?: string;
- hideForMobile?: boolean;
- isMobileHeader?: boolean;
+ mobileOptions?: EuiTableRowCellProps['mobileOptions'];
name: string | React.ReactNode;
render?: (item: T, node: U) => React.ReactNode;
sortable?: boolean | Func;
diff --git a/x-pack/plugins/security_solution/public/common/components/progress_inline/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/progress_inline/__snapshots__/index.test.tsx.snap
index e1fc1becd0188..bf36bf1afeef3 100644
--- a/x-pack/plugins/security_solution/public/common/components/progress_inline/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/progress_inline/__snapshots__/index.test.tsx.snap
@@ -13,7 +13,7 @@ exports[`ProgressInline it renders 1`] = `
className="siemProgressInline__bar"
>
diff --git a/x-pack/plugins/security_solution/public/common/components/progress_inline/index.tsx b/x-pack/plugins/security_solution/public/common/components/progress_inline/index.tsx
index bbea27f5a705a..7b7e24bf8707b 100644
--- a/x-pack/plugins/security_solution/public/common/components/progress_inline/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/progress_inline/index.tsx
@@ -38,7 +38,7 @@ export const ProgressInline = React.memo(
{children}
-
+
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/ml_job_description.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/ml_job_description.tsx
index 27afe847f7612..4b61504ec997a 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/ml_job_description.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/ml_job_description.tsx
@@ -51,7 +51,7 @@ export const AuditIcon = React.memo(AuditIconComponent);
const JobStatusBadgeComponent: React.FC<{ job: MlSummaryJob }> = ({ job }) => {
const isStarted = isJobStarted(job.jobState, job.datafeedState);
- const color = isStarted ? 'secondary' : 'danger';
+ const color = isStarted ? 'success' : 'danger';
const text = isStarted ? ML_JOB_STARTED : ML_JOB_STOPPED;
return (
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx
index 9340ca2af1513..01ba47f728e43 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx
@@ -20,6 +20,7 @@ import {
AboutStepRule,
RuleStepsFormHooks,
RuleStep,
+ DefineStepRule,
} from '../../../pages/detection_engine/rules/types';
import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers';
import { getMockTheme } from '../../../../common/lib/kibana/kibana_react.mock';
@@ -105,6 +106,63 @@ describe.skip('StepAboutRuleComponent', () => {
});
});
+ it('is invalid if threat match rule and threat_indicator_path is not present', async () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ await act(async () => {
+ if (!formHook) {
+ throw new Error('Form hook not set, but tests depend on it');
+ }
+ wrapper
+ .find('[data-test-subj="detectionEngineStepAboutThreatIndicatorPath"] input')
+ .first()
+ .simulate('change', { target: { value: '' } });
+
+ const result = await formHook();
+ expect(result?.isValid).toEqual(false);
+ });
+ });
+
+ it('is valid if is not a threat match rule and threat_indicator_path is not present', async () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ await act(async () => {
+ if (!formHook) {
+ throw new Error('Form hook not set, but tests depend on it');
+ }
+ wrapper
+ .find('[data-test-subj="detectionEngineStepAboutThreatIndicatorPath"] input')
+ .first()
+ .simulate('change', { target: { value: '' } });
+
+ const result = await formHook();
+ expect(result?.isValid).toEqual(true);
+ });
+ });
+
it('is invalid if no "name" is present', async () => {
const wrapper = mount(
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
index 91e428382dc30..5f5b636d6afe1 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
@@ -6,7 +6,7 @@
*/
import { EuiAccordion, EuiFlexItem, EuiSpacer, EuiFormRow } from '@elastic/eui';
-import React, { FC, memo, useCallback, useEffect, useState } from 'react';
+import React, { FC, memo, useCallback, useEffect, useState, useMemo } from 'react';
import styled from 'styled-components';
import {
@@ -31,7 +31,7 @@ import {
import { defaultRiskScoreBySeverity, severityOptions } from './data';
import { stepAboutDefaultValue } from './default_value';
import { isUrlInvalid } from '../../../../common/utils/validators';
-import { schema } from './schema';
+import { schema as defaultSchema, threatIndicatorPathRequiredSchemaValue } from './schema';
import * as I18n from './translations';
import { StepContentWrapper } from '../step_content_wrapper';
import { NextStep } from '../next_step';
@@ -73,7 +73,28 @@ const StepAboutRuleComponent: FC = ({
onSubmit,
setForm,
}) => {
- const initialState = defaultValues ?? stepAboutDefaultValue;
+ const isThreatMatchRuleValue = useMemo(
+ () => isThreatMatchRule(defineRuleData?.ruleType),
+ [defineRuleData?.ruleType]
+ );
+
+ const initialState: AboutStepRule = useMemo(
+ () =>
+ defaultValues ??
+ (isThreatMatchRuleValue
+ ? { ...stepAboutDefaultValue, threatIndicatorPath: DEFAULT_INDICATOR_SOURCE_PATH }
+ : stepAboutDefaultValue),
+ [defaultValues, isThreatMatchRuleValue]
+ );
+
+ const schema = useMemo(
+ () =>
+ isThreatMatchRuleValue
+ ? { ...defaultSchema, threatIndicatorPath: threatIndicatorPathRequiredSchemaValue }
+ : defaultSchema,
+ [isThreatMatchRuleValue]
+ );
+
const [severityValue, setSeverityValue] = useState(initialState.severity.value);
const [indexPatternLoading, { indexPatterns }] = useFetchIndex(defineRuleData?.index ?? []);
@@ -300,7 +321,7 @@ const StepAboutRuleComponent: FC = ({
/>
- {isThreatMatchRule(defineRuleData?.ruleType) && (
+ {isThreatMatchRuleValue && (
<>
= {
labelAppend: OptionalFieldLabel,
},
};
+
+export const threatIndicatorPathRequiredSchemaValue = {
+ type: FIELD_TYPES.TEXT,
+ label: i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathLabel',
+ {
+ defaultMessage: 'Indicator prefix override',
+ }
+ ),
+ helpText: i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathHelpText',
+ {
+ defaultMessage:
+ 'Specify the document prefix containing your indicator fields. Used for enrichment of indicator match alerts.',
+ }
+ ),
+ validations: [
+ {
+ validator: emptyField(
+ i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.threatIndicatorPathFieldEmptyError',
+ {
+ defaultMessage: 'Indicator prefix override must not be empty',
+ }
+ )
+ ),
+ type: VALIDATION_TYPES.FIELD,
+ },
+ ],
+};
diff --git a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.tsx
index 23e5da28a3559..65357b15036ea 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/authentications_table/index.tsx
@@ -140,7 +140,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
{
name: i18n.USER,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
getRowItemDraggables({
rowItems: node.user.name,
@@ -151,7 +151,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
{
name: i18n.SUCCESSES,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) => {
const id = escapeDataProviderId(
`authentications-table-${node._id}-node-successes-${node.successes}`
@@ -189,7 +189,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
{
name: i18n.FAILURES,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) => {
const id = escapeDataProviderId(
`authentications-table-${node._id}-failures-${node.failures}`
@@ -227,7 +227,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
{
name: i18n.LAST_SUCCESSFUL_TIME,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
has('lastSuccess.timestamp', node) && node.lastSuccess?.timestamp != null ? (
@@ -238,7 +238,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
{
name: i18n.LAST_SUCCESSFUL_SOURCE,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
getRowItemDraggables({
rowItems: node.lastSuccess?.source?.ip || null,
@@ -250,7 +250,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
{
name: i18n.LAST_SUCCESSFUL_DESTINATION,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
getRowItemDraggables({
rowItems: node.lastSuccess?.host?.name ?? null,
@@ -262,7 +262,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
{
name: i18n.LAST_FAILED_TIME,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
has('lastFailure.timestamp', node) && node.lastFailure?.timestamp != null ? (
@@ -273,7 +273,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
{
name: i18n.LAST_FAILED_SOURCE,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
getRowItemDraggables({
rowItems: node.lastFailure?.source?.ip || null,
@@ -285,7 +285,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [
{
name: i18n.LAST_FAILED_DESTINATION,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
getRowItemDraggables({
rowItems: node.lastFailure?.host?.name || null,
diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx
index d6c51b2bfe05e..95f88da0a24ac 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx
@@ -31,7 +31,7 @@ export const getHostsColumns = (): HostsTableColumns => [
field: 'node.host.name',
name: i18n.NAME,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
sortable: true,
render: (hostName) => {
if (hostName != null && hostName.length > 0) {
@@ -75,7 +75,7 @@ export const getHostsColumns = (): HostsTableColumns => [
),
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
sortable: true,
render: (lastSeen: Maybe | undefined) => {
if (lastSeen != null && lastSeen.length > 0) {
@@ -92,7 +92,7 @@ export const getHostsColumns = (): HostsTableColumns => [
field: 'node.host.os.name',
name: i18n.OS,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
sortable: false,
render: (hostOsName) => {
if (hostOsName != null) {
@@ -109,7 +109,7 @@ export const getHostsColumns = (): HostsTableColumns => [
field: 'node.host.os.version',
name: i18n.VERSION,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
sortable: false,
render: (hostOsVersion) => {
if (hostOsVersion != null) {
diff --git a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx
index 0541f2f1d403d..0af27bdb0ba18 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/uncommon_process_table/index.tsx
@@ -144,7 +144,7 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [
{
name: i18n.NAME,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
getRowItemDraggables({
rowItems: node.process.name,
@@ -157,7 +157,7 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [
align: 'right',
name: i18n.NUMBER_OF_HOSTS,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) => <>{node.hosts != null ? node.hosts.length : getEmptyValue()}>,
width: '8%',
},
@@ -165,14 +165,14 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [
align: 'right',
name: i18n.NUMBER_OF_INSTANCES,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) => defaultToEmptyTag(node.instances),
width: '8%',
},
{
name: i18n.HOSTS,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
getRowItemDraggables({
rowItems: getHostNames(node),
@@ -185,7 +185,7 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [
{
name: i18n.LAST_COMMAND,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
getRowItemDraggables({
rowItems: node.process != null ? node.process.args : null,
@@ -198,7 +198,7 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [
{
name: i18n.LAST_USER,
truncateText: false,
- hideForMobile: false,
+ mobileOptions: { show: true },
render: ({ node }) =>
getRowItemDraggables({
rowItems: node.user != null ? node.user.name : null,
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.test.tsx b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx
similarity index 95%
rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.test.tsx
rename to x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx
index 3e48ccc6d9b6d..5eebc2721857f 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.test.tsx
@@ -5,15 +5,12 @@
* 2.0.
*/
-import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data';
import { EffectedPolicySelect, EffectedPolicySelectProps } from './effected_policy_select';
-import {
- AppContextTestRender,
- createAppRootMockRenderer,
-} from '../../../../../../common/mock/endpoint';
import React from 'react';
import { forceHTMLElementOffsetWidth } from './test_utils';
import { fireEvent, act } from '@testing-library/react';
+import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
+import { AppContextTestRender, createAppRootMockRenderer } from '../../../common/mock/endpoint';
describe('when using EffectedPolicySelect component', () => {
const generator = new EndpointDocGenerator('effected-policy-select');
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.tsx b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx
similarity index 76%
rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.tsx
rename to x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx
index 07de303c155aa..2249fc89430d2 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/effected_policy_select.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/effected_policy_select.tsx
@@ -23,14 +23,27 @@ import { i18n } from '@kbn/i18n';
import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option';
import { FormattedMessage } from '@kbn/i18n/react';
import styled from 'styled-components';
-import { PolicyData } from '../../../../../../../common/endpoint/types';
-import { getPolicyDetailPath } from '../../../../../common/routing';
-import { useAppUrl } from '../../../../../../common/lib/kibana/hooks';
-import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
-import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator';
+import { PolicyData } from '../../../../common/endpoint/types';
+import { LinkToApp } from '../../../common/components/endpoint/link_to_app';
+import { getPolicyDetailPath } from '../../common/routing';
+import { useTestIdGenerator } from '../hooks/use_test_id_generator';
+import { useAppUrl } from '../../../common/lib/kibana/hooks';
const NOOP = () => {};
const DEFAULT_LIST_PROPS: EuiSelectableProps['listProps'] = { bordered: true, showIcons: false };
+const SEARCH_PROPS = { className: 'effected-policies-search' };
+
+const StyledEuiSelectable = styled.div`
+ .effected-policies-search {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+ .euiSelectableList {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-top-width: 0;
+ }
+`;
const EffectivePolicyFormContainer = styled.div`
.policy-name .euiSelectableListItem__text {
@@ -57,6 +70,7 @@ export type EffectedPolicySelectProps = Omit<
options: PolicyData[];
isGlobal: boolean;
isPlatinumPlus: boolean;
+ description?: string;
onChange: (selection: EffectedPolicySelection) => void;
selected?: PolicyData[];
};
@@ -64,6 +78,7 @@ export const EffectedPolicySelect = memo(
({
isGlobal,
isPlatinumPlus,
+ description,
onChange,
listProps,
options,
@@ -79,7 +94,7 @@ export const EffectedPolicySelect = memo(
() => [
{
id: 'globalPolicy',
- label: i18n.translate('xpack.securitySolution.endpoint.trustedAppsByPolicy.global', {
+ label: i18n.translate('xpack.securitySolution.endpoint.effectedPolicySelect.global', {
defaultMessage: 'Global',
}),
iconType: isGlobal ? 'checkInCircleFilled' : '',
@@ -87,7 +102,7 @@ export const EffectedPolicySelect = memo(
},
{
id: 'perPolicy',
- label: i18n.translate('xpack.securitySolution.endpoint.trustedAppsByPolicy.perPolicy', {
+ label: i18n.translate('xpack.securitySolution.endpoint.effectedPolicySelect.perPolicy', {
defaultMessage: 'Per Policy',
}),
iconType: !isGlobal ? 'checkInCircleFilled' : '',
@@ -169,7 +184,7 @@ export const EffectedPolicySelect = memo(
@@ -179,10 +194,15 @@ export const EffectedPolicySelect = memo(
- {i18n.translate('xpack.securitySolution.trustedApps.assignmentSectionDescription', {
- defaultMessage:
- 'Assign this trusted application globally across all policies, or assign it to specific policies.',
- })}
+ {description
+ ? description
+ : i18n.translate(
+ 'xpack.securitySolution.effectedPolicySelect.assignmentSectionDescription',
+ {
+ defaultMessage:
+ 'Assign globally across all policies, or assign it to specific policies.',
+ }
+ )}
@@ -202,16 +222,19 @@ export const EffectedPolicySelect = memo(
{!isGlobal && (
-
- {...otherSelectableProps}
- options={selectableOptions}
- listProps={listProps || DEFAULT_LIST_PROPS}
- onChange={handleOnPolicySelectChange}
- searchable={true}
- data-test-subj={getTestId('policiesSelectable')}
- >
- {listBuilderCallback}
-
+
+
+ {...otherSelectableProps}
+ options={selectableOptions}
+ listProps={listProps || DEFAULT_LIST_PROPS}
+ onChange={handleOnPolicySelectChange}
+ searchProps={SEARCH_PROPS}
+ searchable={true}
+ data-test-subj={getTestId('policiesSelectable')}
+ >
+ {listBuilderCallback}
+
+
)}
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/index.ts b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/index.ts
similarity index 100%
rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/index.ts
rename to x-pack/plugins/security_solution/public/management/components/effected_policy_select/index.ts
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/test_utils.ts b/x-pack/plugins/security_solution/public/management/components/effected_policy_select/test_utils.ts
similarity index 100%
rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/effected_policy_select/test_utils.ts
rename to x-pack/plugins/security_solution/public/management/components/effected_policy_select/test_utils.ts
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
index 287f66a48fce8..3f4afe8e4b108 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts
@@ -54,7 +54,6 @@ import {
TransformStatsResponse,
} from '../types';
import {
- sendGetEndpointSpecificPackagePolicies,
sendGetEndpointSecurityPackage,
sendGetAgentPolicyList,
sendGetFleetAgentsWithEndpoint,
@@ -81,6 +80,7 @@ import { EndpointPackageInfoStateChanged } from './action';
import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions';
import { getIsInvalidDateRange } from '../utils';
import { METADATA_TRANSFORM_STATS_URL } from '../../../../../common/constants';
+import { sendGetEndpointSpecificPackagePolicies } from '../../../services/policies';
type EndpointPageStore = ImmutableMiddlewareAPI;
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
index 8e0e4fd969c22..8fa4a9388e08e 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
@@ -11,7 +11,7 @@ import { HostStatus, HostPolicyResponseActionStatus } from '../../../../../commo
export const HOST_STATUS_TO_BADGE_COLOR = Object.freeze<{
[key in HostStatus]: string;
}>({
- [HostStatus.HEALTHY]: 'secondary',
+ [HostStatus.HEALTHY]: 'success',
[HostStatus.UNHEALTHY]: 'warning',
[HostStatus.UPDATING]: 'primary',
[HostStatus.OFFLINE]: 'default',
@@ -22,7 +22,7 @@ export const HOST_STATUS_TO_BADGE_COLOR = Object.freeze<{
export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<{
[key in keyof typeof HostPolicyResponseActionStatus]: string;
}>({
- success: 'secondary',
+ success: 'success',
warning: 'warning',
failure: 'danger',
unsupported: 'default',
@@ -31,7 +31,7 @@ export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<{
export const POLICY_STATUS_TO_BADGE_COLOR = Object.freeze<{
[key in keyof typeof HostPolicyResponseActionStatus]: string;
}>({
- success: 'secondary',
+ success: 'success',
warning: 'warning',
failure: 'danger',
unsupported: 'default',
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/services/ingest.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/services/ingest.test.ts
index b5897d8fd3bc4..ef66c948e1127 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/services/ingest.test.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/services/ingest.test.ts
@@ -9,15 +9,9 @@ import {
INGEST_API_EPM_PACKAGES,
sendGetPackagePolicy,
sendGetEndpointSecurityPackage,
- sendGetEndpointSpecificPackagePolicies,
} from './ingest';
import { httpServiceMock } from '../../../../../../../../../src/core/public/mocks';
-import {
- EPM_API_ROUTES,
- PACKAGE_POLICY_SAVED_OBJECT_TYPE,
- PACKAGE_POLICY_API_ROOT,
- PACKAGE_POLICY_API_ROUTES,
-} from '../../../../../../../fleet/common';
+import { EPM_API_ROUTES, PACKAGE_POLICY_API_ROOT } from '../../../../../../../fleet/common';
import { policyListApiPathHandlers } from '../test_mock_utils';
describe('ingest service', () => {
@@ -27,29 +21,6 @@ describe('ingest service', () => {
http = httpServiceMock.createStartContract();
});
- describe('sendGetEndpointSpecificPackagePolicies()', () => {
- it('auto adds kuery to api request', async () => {
- await sendGetEndpointSpecificPackagePolicies(http);
- expect(http.get).toHaveBeenCalledWith(`${PACKAGE_POLICY_API_ROUTES.LIST_PATTERN}`, {
- query: {
- kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
- },
- });
- });
- it('supports additional KQL to be defined on input for query params', async () => {
- await sendGetEndpointSpecificPackagePolicies(http, {
- query: { kuery: 'someValueHere', page: 1, perPage: 10 },
- });
- expect(http.get).toHaveBeenCalledWith(`${PACKAGE_POLICY_API_ROUTES.LIST_PATTERN}`, {
- query: {
- kuery: `someValueHere and ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
- perPage: 10,
- page: 1,
- },
- });
- });
- });
-
describe('sendGetPackagePolicy()', () => {
it('builds correct API path', async () => {
await sendGetPackagePolicy(http, '123');
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/services/ingest.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/services/ingest.ts
index 0375b0c434f18..27b567c3bb38c 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/services/ingest.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/services/ingest.ts
@@ -7,17 +7,15 @@
import { HttpFetchOptions, HttpStart } from 'kibana/public';
import {
- GetPackagePoliciesRequest,
GetAgentStatusResponse,
GetAgentsResponse,
DeletePackagePoliciesResponse,
DeletePackagePoliciesRequest,
- PACKAGE_POLICY_SAVED_OBJECT_TYPE,
GetPackagesResponse,
GetAgentPoliciesRequest,
GetAgentPoliciesResponse,
} from '../../../../../../../fleet/common';
-import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../types';
+import { GetPolicyResponse, UpdatePolicyResponse } from '../../types';
import { NewPolicyData } from '../../../../../../common/endpoint/types';
const INGEST_API_ROOT = `/api/fleet`;
@@ -28,27 +26,6 @@ export const INGEST_API_FLEET_AGENTS = `${INGEST_API_ROOT}/agents`;
export const INGEST_API_EPM_PACKAGES = `${INGEST_API_ROOT}/epm/packages`;
const INGEST_API_DELETE_PACKAGE_POLICY = `${INGEST_API_PACKAGE_POLICIES}/delete`;
-/**
- * Retrieves a list of endpoint specific package policies (those created with a `package.name` of
- * `endpoint`) from Ingest
- * @param http
- * @param options
- */
-export const sendGetEndpointSpecificPackagePolicies = (
- http: HttpStart,
- options: HttpFetchOptions & Partial = {}
-): Promise => {
- return http.get(INGEST_API_PACKAGE_POLICIES, {
- ...options,
- query: {
- ...options.query,
- kuery: `${
- options?.query?.kuery ? `${options.query.kuery} and ` : ''
- }${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: endpoint`,
- },
- });
-};
-
/**
* Retrieves a single package policy based on ID from ingest
* @param http
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.test.tsx
index 30c95472e4d6d..0981c775e961b 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.test.tsx
@@ -97,8 +97,8 @@ describe('Fleet event filters card', () => {
};
});
const component = await renderComponent();
- expect(component.getByText('Event Filters')).not.toBeNull();
- expect(component.getByText('Manage event filters')).not.toBeNull();
+ expect(component.getByText('Event filters')).not.toBeNull();
+ expect(component.getByText('Manage')).not.toBeNull();
});
it('should render an error toast when api call fails', async () => {
expect(addDanger).toBeCalledTimes(0);
@@ -109,8 +109,8 @@ describe('Fleet event filters card', () => {
};
});
const component = await renderComponent();
- expect(component.getByText('Event Filters')).not.toBeNull();
- expect(component.getByText('Manage event filters')).not.toBeNull();
+ expect(component.getByText('Event filters')).not.toBeNull();
+ expect(component.getByText('Manage')).not.toBeNull();
await reactTestingLibrary.waitFor(() => expect(addDanger).toBeCalledTimes(1));
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx
index 41768f4be7d2e..7db76fee4efc5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_event_filters_card.tsx
@@ -95,7 +95,7 @@ export const FleetEventFiltersCard = memo(
@@ -115,7 +115,7 @@ export const FleetEventFiltersCard = memo(
>
>
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_host_isolation_exceptions_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_host_isolation_exceptions_card.test.tsx
new file mode 100644
index 0000000000000..ea1937c5a98d5
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_host_isolation_exceptions_card.test.tsx
@@ -0,0 +1,110 @@
+/*
+ * 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 { I18nProvider } from '@kbn/i18n/react';
+import * as reactTestingLibrary from '@testing-library/react';
+import React from 'react';
+import { ThemeProvider } from 'styled-components';
+import { GetExceptionSummaryResponse } from '../../../../../../../../common/endpoint/types';
+import { getMockTheme } from '../../../../../../../../public/common/lib/kibana/kibana_react.mock';
+import { useToasts } from '../../../../../../../common/lib/kibana';
+import { getHostIsolationExceptionSummary } from '../../../../../host_isolation_exceptions/service';
+import { FleetHostIsolationExceptionsCard } from './fleet_host_isolation_exceptions_card';
+
+jest.mock('./exception_items_summary');
+jest.mock('../../../../../host_isolation_exceptions/service');
+
+jest.mock('../../../../../../../../../../../src/plugins/kibana_react/public', () => {
+ const originalModule = jest.requireActual(
+ '../../../../../../../../../../../src/plugins/kibana_react/public'
+ );
+ const useKibana = jest.fn().mockImplementation(() => ({
+ services: {
+ http: {},
+ data: {},
+ notifications: {},
+ application: {
+ getUrlForApp: jest.fn(),
+ },
+ },
+ }));
+
+ return {
+ ...originalModule,
+ useKibana,
+ };
+});
+
+jest.mock('../../../../../../../common/lib/kibana');
+
+const mockTheme = getMockTheme({
+ eui: {
+ paddingSizes: { m: '2' },
+ },
+});
+
+const getHostIsolationExceptionSummaryMock = getHostIsolationExceptionSummary as jest.Mock;
+const useToastsMock = useToasts as jest.Mock;
+
+const summary: GetExceptionSummaryResponse = {
+ windows: 3,
+ linux: 2,
+ macos: 2,
+ total: 7,
+};
+
+describe('Fleet host isolation exceptions card filters card', () => {
+ let promise: Promise;
+ let addDanger: jest.Mock = jest.fn();
+ const renderComponent: () => Promise = async () => {
+ const Wrapper: React.FC = ({ children }) => (
+
+ {children}
+
+ );
+ // @ts-expect-error TS2739
+ const component = reactTestingLibrary.render(, {
+ wrapper: Wrapper,
+ });
+ try {
+ // @ts-expect-error TS2769
+ await reactTestingLibrary.act(() => promise);
+ } catch (err) {
+ return component;
+ }
+ return component;
+ };
+ beforeAll(() => {
+ useToastsMock.mockImplementation(() => {
+ return {
+ addDanger,
+ };
+ });
+ });
+ beforeEach(() => {
+ promise = Promise.resolve(summary);
+ addDanger = jest.fn();
+ });
+ afterEach(() => {
+ getHostIsolationExceptionSummaryMock.mockReset();
+ });
+ it('should render correctly', async () => {
+ getHostIsolationExceptionSummaryMock.mockReturnValueOnce(promise);
+ const component = await renderComponent();
+ expect(component.getByText('Host isolation exceptions')).not.toBeNull();
+ expect(component.getByText('Manage')).not.toBeNull();
+ });
+ it('should render an error toast when api call fails', async () => {
+ expect(addDanger).toBeCalledTimes(0);
+ promise = Promise.reject(new Error('error test'));
+ getHostIsolationExceptionSummaryMock.mockReturnValueOnce(promise);
+ const component = await renderComponent();
+ expect(component.getByText('Host isolation exceptions')).not.toBeNull();
+ expect(component.getByText('Manage')).not.toBeNull();
+ await reactTestingLibrary.waitFor(() => expect(addDanger).toBeCalledTimes(1));
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_host_isolation_exceptions_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_host_isolation_exceptions_card.tsx
new file mode 100644
index 0000000000000..535c0be4736cc
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_host_isolation_exceptions_card.tsx
@@ -0,0 +1,130 @@
+/*
+ * 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 { EuiPanel, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
+import { INTEGRATIONS_PLUGIN_ID } from '../../../../../../../../../fleet/common';
+import {
+ PackageCustomExtensionComponentProps,
+ pagePathGetters,
+} from '../../../../../../../../../fleet/public';
+import {
+ GetExceptionSummaryResponse,
+ ListPageRouteState,
+} from '../../../../../../../../common/endpoint/types';
+import { useKibana, useToasts } from '../../../../../../../common/lib/kibana';
+import { useAppUrl } from '../../../../../../../common/lib/kibana/hooks';
+import { getHostIsolationExceptionsListPath } from '../../../../../../common/routing';
+import { getHostIsolationExceptionSummary } from '../../../../../host_isolation_exceptions/service';
+import { ExceptionItemsSummary } from './exception_items_summary';
+import { LinkWithIcon } from './link_with_icon';
+import { StyledEuiFlexGridGroup, StyledEuiFlexGridItem } from './styled_components';
+
+export const FleetHostIsolationExceptionsCard = memo(
+ ({ pkgkey }) => {
+ const { getAppUrl } = useAppUrl();
+ const {
+ services: { http },
+ } = useKibana();
+ const toasts = useToasts();
+ const [stats, setStats] = useState();
+ const hostIsolationExceptionsListUrlPath = getHostIsolationExceptionsListPath();
+ const isMounted = useRef();
+
+ useEffect(() => {
+ isMounted.current = true;
+ const fetchStats = async () => {
+ try {
+ const summary = await getHostIsolationExceptionSummary(http);
+ if (isMounted.current) {
+ setStats(summary);
+ }
+ } catch (error) {
+ if (isMounted.current) {
+ toasts.addDanger(
+ i18n.translate(
+ 'xpack.securitySolution.endpoint.fleetCustomExtension.hostIsolationExceptionsSummary.error',
+ {
+ defaultMessage:
+ 'There was an error trying to fetch host isolation exceptions stats: "{error}"',
+ values: { error },
+ }
+ )
+ );
+ }
+ }
+ };
+ fetchStats();
+ return () => {
+ isMounted.current = false;
+ };
+ }, [http, toasts]);
+
+ const hostIsolationExceptionsRouteState = useMemo(() => {
+ const fleetPackageCustomUrlPath = `#${
+ pagePathGetters.integration_details_custom({ pkgkey })[1]
+ }`;
+ return {
+ backButtonLabel: i18n.translate(
+ 'xpack.securitySolution.endpoint.fleetCustomExtension.hostIsolationExceptionsSummary.backButtonLabel',
+ { defaultMessage: 'Back to Endpoint Integration' }
+ ),
+ onBackButtonNavigateTo: [
+ INTEGRATIONS_PLUGIN_ID,
+ {
+ path: fleetPackageCustomUrlPath,
+ },
+ ],
+ backButtonUrl: getAppUrl({
+ appId: INTEGRATIONS_PLUGIN_ID,
+ path: fleetPackageCustomUrlPath,
+ }),
+ };
+ }, [getAppUrl, pkgkey]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <>
+
+
+
+ >
+
+
+
+ );
+ }
+);
+
+FleetHostIsolationExceptionsCard.displayName = 'FleetHostIsolationExceptionsCard';
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.test.tsx
index c61f109c75a1e..1da2c41324f56 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.test.tsx
@@ -100,8 +100,8 @@ describe('Fleet trusted apps card', () => {
};
});
const component = await renderComponent();
- expect(component.getByText('Trusted Applications')).not.toBeNull();
- expect(component.getByText('Manage trusted applications')).not.toBeNull();
+ expect(component.getByText('Trusted applications')).not.toBeNull();
+ expect(component.getByText('Manage')).not.toBeNull();
});
it('should render an error toast when api call fails', async () => {
expect(addDanger).toBeCalledTimes(0);
@@ -112,8 +112,8 @@ describe('Fleet trusted apps card', () => {
};
});
const component = await renderComponent();
- expect(component.getByText('Trusted Applications')).not.toBeNull();
- expect(component.getByText('Manage trusted applications')).not.toBeNull();
+ expect(component.getByText('Trusted applications')).not.toBeNull();
+ expect(component.getByText('Manage')).not.toBeNull();
await reactTestingLibrary.waitFor(() => expect(addDanger).toBeCalledTimes(1));
});
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx
index 54b1c37c7093f..680023cc6fd07 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx
@@ -72,7 +72,7 @@ export const FleetTrustedAppsCard = memo(
const getTitleMessage = () => (
);
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card_wrapper.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card_wrapper.tsx
index 5ac79a5dd5d5a..848a0916b1d2e 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card_wrapper.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card_wrapper.tsx
@@ -59,8 +59,8 @@ export const FleetTrustedAppsCardWrapper = memo
),
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/styled_components.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/styled_components.tsx
index ad1d823677f22..ce28b4ac6c1cc 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/styled_components.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/styled_components.tsx
@@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
export const StyledEuiFlexGridGroup = styled(EuiFlexGroup)`
display: grid;
- grid-template-columns: 25% 45% 30%;
+ grid-template-columns: 33% 45% 22%;
grid-template-areas: 'title summary link';
`;
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx
index 0748a95f63c9f..d53fe308a90ec 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx
@@ -8,8 +8,9 @@
import { EuiSpacer } from '@elastic/eui';
import React, { memo } from 'react';
import { PackageCustomExtensionComponentProps } from '../../../../../../../../fleet/public';
-import { FleetTrustedAppsCardWrapper } from './components/fleet_trusted_apps_card_wrapper';
import { FleetEventFiltersCard } from './components/fleet_event_filters_card';
+import { FleetHostIsolationExceptionsCard } from './components/fleet_host_isolation_exceptions_card';
+import { FleetTrustedAppsCardWrapper } from './components/fleet_trusted_apps_card_wrapper';
export const EndpointPackageCustomExtension = memo(
(props) => {
@@ -18,6 +19,8 @@ export const EndpointPackageCustomExtension = memo
+
+