+
{embeddablePanelContextMenu}
{ariaLabelElement}
@@ -104,7 +104,7 @@ export const EmbeddablePanelHeader = ({
hideTitle={hideTitle}
embeddable={embeddable}
description={description}
- customizePanelAction={universalActions.customizePanel}
+ editPanelAction={universalActions.editPanel}
/>
{showBadges && badgeComponents}
diff --git a/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_title.tsx b/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_title.tsx
index 734b420d04052..693215a3084ca 100644
--- a/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_title.tsx
+++ b/src/plugins/embeddable/public/embeddable_panel/panel_header/embeddable_panel_title.tsx
@@ -11,21 +11,22 @@ import React, { useMemo } from 'react';
import { EuiIcon, EuiLink, EuiToolTip } from '@elastic/eui';
import { IEmbeddable, ViewMode } from '../../lib';
-import { CustomizePanelAction } from '../panel_actions';
import { getEditTitleAriaLabel, placeholderTitle } from '../embeddable_panel_strings';
+import { EditPanelAction } from '../panel_actions';
+import { openCustomizePanelFlyout } from '../panel_actions/customize_panel_action/open_customize_panel';
export const EmbeddablePanelTitle = ({
viewMode,
hideTitle,
embeddable,
description,
- customizePanelAction,
+ editPanelAction,
}: {
hideTitle?: boolean;
viewMode?: ViewMode;
description?: string;
embeddable: IEmbeddable;
- customizePanelAction?: CustomizePanelAction;
+ editPanelAction?: EditPanelAction;
}) => {
const title = embeddable.getTitle();
@@ -39,32 +40,44 @@ export const EmbeddablePanelTitle = ({
if (viewMode === ViewMode.VIEW) {
return
{title};
}
- if (customizePanelAction) {
+ if (editPanelAction) {
return (
customizePanelAction.execute({ embeddable })}
+ onClick={() =>
+ openCustomizePanelFlyout({
+ editPanel: editPanelAction,
+ embeddable,
+ focusOnTitle: true,
+ })
+ }
>
{title || placeholderTitle}
);
}
return null;
- }, [customizePanelAction, embeddable, title, viewMode, hideTitle]);
+ }, [editPanelAction, embeddable, title, viewMode, hideTitle]);
const titleComponentWithDescription = useMemo(() => {
- if (!description) return
{titleComponent};
+ if (!description)
+ return (
+
+ {titleComponent}
+
+ );
return (
-
+
{titleComponent}{' '}
{
this.appList = appList;
@@ -168,13 +167,7 @@ export class EmbeddablePublicPlugin implements Plugin {
+// Failing: See https://github.com/elastic/kibana/issues/174194
+// Failing: See https://github.com/elastic/kibana/issues/174195
+describe.skip('EditAssigneesFlyout', () => {
let appMock: AppMockRenderer;
/**
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx
index a1d0fb484b44f..81753d9c56c38 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/use_output_form.tsx
@@ -399,7 +399,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) {
);
const kafkaSslKeySecretInput = useSecretInput(
- kafkaOutput?.ssl?.certificate,
+ kafkaOutput?.secrets?.ssl?.key,
kafkaAuthMethodInput.value === kafkaAuthType.Ssl ? validateSSLKeySecret : undefined,
isSSLEditable
);
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
index db38796d2ab3b..e306398c541c7 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
@@ -71,7 +71,7 @@ describe('Data Streams tab', () => {
expect(exists('emptyPrompt')).toBe(true);
});
- test('when Ingest Manager is disabled, goes to index templates tab when "Get started" link is clicked', async () => {
+ test('when Fleet is disabled, goes to index templates tab when "Get started" link is clicked', async () => {
testBed = await setup(httpSetup, {
plugins: {},
url: urlServiceMock,
@@ -796,7 +796,7 @@ describe('Data Streams tab', () => {
_meta: {
package: 'test',
managed: true,
- managed_by: 'ingest-manager',
+ managed_by: 'fleet',
},
});
const nonManagedDataStream = createDataStreamPayload({ name: 'non-managed-data-stream' });
@@ -813,19 +813,12 @@ describe('Data Streams tab', () => {
testBed.component.update();
});
- test('listed in the table with Fleet-managed label', () => {
+ test('listed in the table with managed label', () => {
const { table } = testBed;
const { tableCellsValues } = table.getMetaData('dataStreamTable');
expect(tableCellsValues).toEqual([
- [
- '',
- `managed-data-stream${nonBreakingSpace}Fleet-managed`,
- 'green',
- '1',
- '7 days',
- 'Delete',
- ],
+ ['', `managed-data-stream${nonBreakingSpace}Managed`, 'green', '1', '7 days', 'Delete'],
['', 'non-managed-data-stream', 'green', '1', '7 days', 'Delete'],
]);
});
@@ -835,14 +828,7 @@ describe('Data Streams tab', () => {
let { tableCellsValues } = table.getMetaData('dataStreamTable');
expect(tableCellsValues).toEqual([
- [
- '',
- `managed-data-stream${nonBreakingSpace}Fleet-managed`,
- 'green',
- '1',
- '7 days',
- 'Delete',
- ],
+ ['', `managed-data-stream${nonBreakingSpace}Managed`, 'green', '1', '7 days', 'Delete'],
['', 'non-managed-data-stream', 'green', '1', '7 days', 'Delete'],
]);
diff --git a/x-pack/plugins/index_management/public/application/lib/data_streams.tsx b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx
index c16b28f73410a..44852c5dc3230 100644
--- a/x-pack/plugins/index_management/public/application/lib/data_streams.tsx
+++ b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx
@@ -12,9 +12,8 @@ import { EuiIcon, EuiToolTip } from '@elastic/eui';
import { splitSizeAndUnits, DataStream } from '../../../common';
import { timeUnits, extraTimeUnits } from '../constants/time_units';
-export const isFleetManaged = (dataStream: DataStream): boolean => {
- // TODO check if the wording will change to 'fleet'
- return Boolean(dataStream._meta?.managed && dataStream._meta?.managed_by === 'ingest-manager');
+export const isManaged = (dataStream: DataStream): boolean => {
+ return Boolean(dataStream._meta?.managed);
};
export const filterDataStreams = (
@@ -23,13 +22,13 @@ export const filterDataStreams = (
): DataStream[] => {
return dataStreams.filter((dataStream: DataStream) => {
// include all data streams that are neither hidden nor managed
- if (!dataStream.hidden && !isFleetManaged(dataStream)) {
+ if (!dataStream.hidden && !isManaged(dataStream)) {
return true;
}
if (dataStream.hidden && visibleTypes.includes('hidden')) {
return true;
}
- return isFleetManaged(dataStream) && visibleTypes.includes('managed');
+ return isManaged(dataStream) && visibleTypes.includes('managed');
});
};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_badges.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_badges.tsx
index 39c6f61e75dde..2f98b6ac357a4 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_badges.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_badges.tsx
@@ -9,7 +9,7 @@ import React from 'react';
import { EuiBadge, EuiBadgeGroup } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { DataStream } from '../../../../../common';
-import { isFleetManaged } from '../../../lib/data_streams';
+import { isManaged } from '../../../lib/data_streams';
interface Props {
dataStream: DataStream;
@@ -17,12 +17,12 @@ interface Props {
export const DataStreamsBadges: React.FunctionComponent = ({ dataStream }) => {
const badges = [];
- if (isFleetManaged(dataStream)) {
+ if (isManaged(dataStream)) {
badges.push(
);
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
index fa4efa61bf548..125f676897ffb 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
@@ -84,7 +84,7 @@ export const DataStreamList: React.FunctionComponent>({
managed: {
name: i18n.translate('xpack.idxMgmt.dataStreamList.viewManagedLabel', {
- defaultMessage: 'Fleet-managed data streams',
+ defaultMessage: 'Managed data streams',
}),
checked: 'on',
},
@@ -226,7 +226,7 @@ export const DataStreamList: React.FunctionComponent
{i18n.translate(
- 'xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerLink',
+ 'xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaFleetLink',
{
defaultMessage: 'Fleet',
}
diff --git a/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx
index e88d354acf68b..7cf4c6bfff66b 100644
--- a/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx
+++ b/x-pack/plugins/lens/public/visualizations/partition/dimension_editor.tsx
@@ -140,6 +140,7 @@ export function DimensionEditor(props: DimensionEditorProps) {
defaultMessage: 'Color mapping',
})}
style={{ alignItems: 'center' }}
+ fullWidth
>
{
+ if (riskScore >= RISK_LEVEL_RANGES[RiskLevels.critical].start) {
+ return RiskLevels.critical;
+ } else if (riskScore >= RISK_LEVEL_RANGES[RiskLevels.high].start) {
+ return RiskLevels.high;
+ } else if (riskScore >= RISK_LEVEL_RANGES[RiskLevels.moderate].start) {
+ return RiskLevels.moderate;
+ } else if (riskScore >= RISK_LEVEL_RANGES[RiskLevels.low].start) {
+ return RiskLevels.low;
+ } else {
+ return RiskLevels.unknown;
+ }
+};
diff --git a/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/types.ts b/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/types.ts
index deed9767ed2cd..f803ec0ed6711 100644
--- a/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/types.ts
+++ b/x-pack/plugins/security_solution/common/entity_analytics/risk_engine/types.ts
@@ -38,24 +38,41 @@ export interface SimpleRiskInput {
export interface EcsRiskScore {
'@timestamp': string;
host?: {
+ name: string;
risk: Omit;
};
user?: {
+ name: string;
risk: Omit;
};
}
export type RiskInputs = SimpleRiskInput[];
+/**
+ * The API response object representing a risk score
+ */
export interface RiskScore {
'@timestamp': string;
id_field: string;
id_value: string;
+ criticality_level?: string | undefined;
+ criticality_modifier?: number | undefined;
calculated_level: string;
calculated_score: number;
calculated_score_norm: number;
category_1_score: number;
category_1_count: number;
+ category_2_score?: number;
+ category_2_count?: number;
notes: string[];
inputs: RiskInputs;
}
+
+export enum RiskLevels {
+ unknown = 'Unknown',
+ low = 'Low',
+ moderate = 'Moderate',
+ high = 'High',
+ critical = 'Critical',
+}
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts
index f33eb664628a1..7810c29fbfbfe 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/risk_score/all/index.ts
@@ -8,7 +8,10 @@
import type { IEsSearchResponse } from '@kbn/data-plugin/common';
import type { Inspect, Maybe, SortField } from '../../../common';
-import type { RiskInputs } from '../../../../entity_analytics/risk_engine';
+import {
+ type RiskInputs,
+ RiskLevels as RiskSeverity,
+} from '../../../../entity_analytics/risk_engine';
export interface HostsRiskScoreStrategyResponse extends IEsSearchResponse {
inspect?: Maybe;
@@ -30,6 +33,8 @@ export interface RiskStats {
inputs?: RiskInputs;
}
+export { RiskSeverity };
+
export interface HostRiskScore {
'@timestamp': string;
host: {
@@ -85,14 +90,6 @@ export interface RiskScoreItem {
[RiskScoreFields.alertsCount]: Maybe;
}
-export enum RiskSeverity {
- unknown = 'Unknown',
- low = 'Low',
- moderate = 'Moderate',
- high = 'High',
- critical = 'Critical',
-}
-
export const isUserRiskScore = (risk: HostRiskScore | UserRiskScore): risk is UserRiskScore =>
'user' in risk;
diff --git a/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts b/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts
index f6009c85dbd43..b8735c2e7b6fb 100644
--- a/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts
+++ b/x-pack/plugins/security_solution/public/entity_analytics/common/utils.ts
@@ -8,6 +8,7 @@
import { euiLightVars } from '@kbn/ui-theme';
import { RiskSeverity } from '../../../common/search_strategy';
import { SEVERITY_COLOR } from '../../overview/components/detection_response/utils';
+export { RISK_LEVEL_RANGES as RISK_SCORE_RANGES } from '../../../common/entity_analytics/risk_engine';
export const SEVERITY_UI_SORT_ORDER = [
RiskSeverity.unknown,
@@ -25,14 +26,6 @@ export const RISK_SEVERITY_COLOUR: { [k in RiskSeverity]: string } = {
[RiskSeverity.critical]: SEVERITY_COLOR.critical,
};
-export const RISK_SCORE_RANGES = {
- [RiskSeverity.unknown]: { start: 0, stop: 20 },
- [RiskSeverity.low]: { start: 20, stop: 40 },
- [RiskSeverity.moderate]: { start: 40, stop: 70 },
- [RiskSeverity.high]: { start: 70, stop: 90 },
- [RiskSeverity.critical]: { start: 90, stop: 100 },
-};
-
type SnakeToCamelCaseString = S extends `${infer T}_${infer U}`
? `${T}${Capitalize>}`
: S;
diff --git a/x-pack/plugins/security_solution/public/timelines/store/helpers.ts b/x-pack/plugins/security_solution/public/timelines/store/helpers.ts
index 3206bb96e89ad..456d716f529f5 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/helpers.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/helpers.ts
@@ -1475,7 +1475,13 @@ export const setInitializeTimelineSettings = ({
initialized: true,
},
}
- : timelineById;
+ : {
+ ...timelineById,
+ [id]: {
+ ...timeline,
+ ...timelineSettingsProps,
+ },
+ };
};
interface ApplyDeltaToTableColumnWidth {
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.test.ts
index 7c88d5c9192ee..8ae230d65506b 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.test.ts
@@ -17,18 +17,23 @@ import {
import { requestContextMock, serverMock, requestMock } from '../__mocks__';
import { querySignalsRoute } from './query_signals_route';
import { ruleRegistryMocks } from '@kbn/rule-registry-plugin/server/mocks';
+import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks';
describe('query for signal', () => {
let server: ReturnType;
let { context } = requestContextMock.createTools();
- const ruleDataClient = ruleRegistryMocks.createRuleDataClient('.alerts-security.alerts');
+ context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
+ elasticsearchClientMock.createSuccessTransportRequestPromise(getEmptySignalsResponse())
+ );
+ const ruleDataClient = ruleRegistryMocks.createRuleDataClient('.alerts-security.alerts-');
beforeEach(() => {
server = serverMock.create();
({ context } = requestContextMock.createTools());
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ruleDataClient.getReader().search.mockResolvedValue(getEmptySignalsResponse() as any);
+ context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ getEmptySignalsResponse() as any
+ );
querySignalsRoute(server.router, ruleDataClient);
});
@@ -41,7 +46,7 @@ describe('query for signal', () => {
);
expect(response.status).toEqual(200);
- expect(ruleDataClient.getReader().search).toHaveBeenCalledWith(
+ expect(context.core.elasticsearch.client.asCurrentUser.search).toHaveBeenCalledWith(
expect.objectContaining({
body: typicalSignalsQuery(),
})
@@ -55,9 +60,9 @@ describe('query for signal', () => {
);
expect(response.status).toEqual(200);
- expect(ruleDataClient.getReader).toHaveBeenCalledWith(
+ expect(context.core.elasticsearch.client.asCurrentUser.search).toHaveBeenCalledWith(
expect.objectContaining({
- namespace: 'default',
+ index: '.alerts-security.alerts-default',
})
);
});
@@ -69,7 +74,7 @@ describe('query for signal', () => {
);
expect(response.status).toEqual(200);
- expect(ruleDataClient.getReader().search).toHaveBeenCalledWith(
+ expect(context.core.elasticsearch.client.asCurrentUser.search).toHaveBeenCalledWith(
expect.objectContaining({ body: typicalSignalsQueryAggs(), ignore_unavailable: true })
);
});
@@ -81,7 +86,7 @@ describe('query for signal', () => {
);
expect(response.status).toEqual(200);
- expect(ruleDataClient.getReader().search).toHaveBeenCalledWith(
+ expect(context.core.elasticsearch.client.asCurrentUser.search).toHaveBeenCalledWith(
expect.objectContaining({
body: {
...typicalSignalsQuery(),
@@ -92,7 +97,9 @@ describe('query for signal', () => {
});
test('catches error if query throws error', async () => {
- ruleDataClient.getReader().search.mockRejectedValue(new Error('Test error'));
+ context.core.elasticsearch.client.asCurrentUser.search.mockRejectedValue(
+ new Error('Test error')
+ );
const response = await server.inject(
getSignalsAggsQueryRequest(),
requestContextMock.convertContext(context)
diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts
index 673b6c2d85818..3bd52ebdaacda 100644
--- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts
+++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/query_signals_route.ts
@@ -40,6 +40,8 @@ export const querySignalsRoute = (
},
},
async (context, request, response) => {
+ const esClient = (await context.core).elasticsearch.client.asCurrentUser;
+
// eslint-disable-next-line @typescript-eslint/naming-convention
const { query, aggs, _source, fields, track_total_hits, size, runtime_mappings, sort } =
request.body;
@@ -58,10 +60,11 @@ export const querySignalsRoute = (
body: '"value" must have at least 1 children',
});
}
-
try {
const spaceId = (await context.securitySolution).getSpaceId();
- const result = await ruleDataClient?.getReader({ namespace: spaceId }).search({
+ const indexPattern = ruleDataClient?.indexNameWithNamespace(spaceId);
+ const result = await esClient.search({
+ index: indexPattern,
body: {
query,
// Note: I use a spread operator to please TypeScript with aggs: { ...aggs }
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.mock.ts
index eaf3e9a3ec22e..6b98e880ea6ab 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.mock.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.mock.ts
@@ -12,6 +12,7 @@ const createAssetCriticalityDataClientMock = () =>
doesIndexExist: jest.fn(),
getStatus: jest.fn(),
init: jest.fn(),
+ search: jest.fn(),
} as unknown as jest.Mocked);
export const assetCriticalityDataClientMock = { create: createAssetCriticalityDataClientMock };
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.test.ts
index c06586516d682..83d197123303a 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.test.ts
@@ -57,4 +57,68 @@ describe('AssetCriticalityDataClient', () => {
});
});
});
+
+ describe('#search()', () => {
+ let esClientMock: ReturnType<
+ typeof elasticsearchServiceMock.createScopedClusterClient
+ >['asInternalUser'];
+ let loggerMock: ReturnType;
+ let subject: AssetCriticalityDataClient;
+
+ beforeEach(() => {
+ esClientMock = elasticsearchServiceMock.createScopedClusterClient().asInternalUser;
+ loggerMock = loggingSystemMock.createLogger();
+ subject = new AssetCriticalityDataClient({
+ esClient: esClientMock,
+ logger: loggerMock,
+ namespace: 'default',
+ });
+ });
+
+ it('searches in the asset criticality index', async () => {
+ subject.search({ query: { match_all: {} } });
+
+ expect(esClientMock.search).toHaveBeenCalledWith(
+ expect.objectContaining({ index: '.asset-criticality.asset-criticality-default' })
+ );
+ });
+
+ it('requires a query parameter', async () => {
+ subject.search({ query: { match_all: {} } });
+
+ expect(esClientMock.search).toHaveBeenCalledWith(
+ expect.objectContaining({ body: { query: { match_all: {} } } })
+ );
+ });
+
+ it('accepts a size parameter', async () => {
+ subject.search({ query: { match_all: {} }, size: 100 });
+
+ expect(esClientMock.search).toHaveBeenCalledWith(expect.objectContaining({ size: 100 }));
+ });
+
+ it('defaults to the default query size', async () => {
+ subject.search({ query: { match_all: {} } });
+ const defaultSize = 1_000;
+
+ expect(esClientMock.search).toHaveBeenCalledWith(
+ expect.objectContaining({ size: defaultSize })
+ );
+ });
+
+ it('caps the size to the maximum query size', async () => {
+ subject.search({ query: { match_all: {} }, size: 999999 });
+ const maxSize = 100_000;
+
+ expect(esClientMock.search).toHaveBeenCalledWith(expect.objectContaining({ size: maxSize }));
+ });
+
+ it('ignores an index_not_found_exception if the criticality index does not exist', async () => {
+ subject.search({ query: { match_all: {} } });
+
+ expect(esClientMock.search).toHaveBeenCalledWith(
+ expect.objectContaining({ ignore_unavailable: true })
+ );
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts
index afddfd6893240..3ac20360bcae0 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts
@@ -4,12 +4,14 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+import type { ESFilter } from '@kbn/es-types';
+import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types';
import type { Logger, ElasticsearchClient } from '@kbn/core/server';
import { mappingFromFieldMap } from '@kbn/alerting-plugin/common';
-import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics/asset_criticality';
+import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics';
import { createOrUpdateIndex } from '../utils/create_or_update_index';
import { getAssetCriticalityIndex } from '../../../../common/entity_analytics/asset_criticality';
-import { assetCriticalityFieldMap } from './configurations';
+import { assetCriticalityFieldMap } from './constants';
interface AssetCriticalityClientOpts {
logger: Logger;
@@ -25,7 +27,11 @@ interface AssetCriticalityUpsert {
type AssetCriticalityIdParts = Pick;
+const MAX_CRITICALITY_RESPONSE_SIZE = 100_000;
+const DEFAULT_CRITICALITY_RESPONSE_SIZE = 1_000;
+
const createId = ({ idField, idValue }: AssetCriticalityIdParts) => `${idField}:${idValue}`;
+
export class AssetCriticalityDataClient {
constructor(private readonly options: AssetCriticalityClientOpts) {}
/**
@@ -43,6 +49,29 @@ export class AssetCriticalityDataClient {
});
}
+ /**
+ *
+ * A general method for searching asset criticality records.
+ * @param query an ESL query to filter criticality results
+ * @param size the maximum number of records to return. Cannot exceed {@link MAX_CRITICALITY_RESPONSE_SIZE}. If unspecified, will default to {@link DEFAULT_CRITICALITY_RESPONSE_SIZE}.
+ * @returns criticality records matching the query
+ */
+ public async search({
+ query,
+ size,
+ }: {
+ query: ESFilter;
+ size?: number;
+ }): Promise> {
+ const response = await this.options.esClient.search({
+ index: this.getIndex(),
+ ignore_unavailable: true,
+ body: { query },
+ size: Math.min(size ?? DEFAULT_CRITICALITY_RESPONSE_SIZE, MAX_CRITICALITY_RESPONSE_SIZE),
+ });
+ return response;
+ }
+
private getIndex() {
return getAssetCriticalityIndex(this.options.namespace);
}
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.mock.ts
new file mode 100644
index 0000000000000..9de2d8c6bae2c
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.mock.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 type { AssetCriticalityService } from './asset_criticality_service';
+
+const buildMockAssetCriticalityService = (): jest.Mocked => ({
+ getCriticalitiesByIdentifiers: jest.fn().mockResolvedValue([]),
+ isEnabled: jest.fn().mockReturnValue(true),
+});
+
+export const assetCriticalityServiceMock = {
+ create: buildMockAssetCriticalityService,
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.test.ts
new file mode 100644
index 0000000000000..7075aacfcb31d
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.test.ts
@@ -0,0 +1,206 @@
+/*
+ * 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 type { SearchHit } from '@elastic/elasticsearch/lib/api/types';
+
+import type { ExperimentalFeatures } from '../../../../common';
+import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics';
+import type { AssetCriticalityDataClient } from './asset_criticality_data_client';
+import { assetCriticalityDataClientMock } from './asset_criticality_data_client.mock';
+import {
+ type AssetCriticalityService,
+ assetCriticalityServiceFactory,
+} from './asset_criticality_service';
+
+const buildMockCriticalityHit = (
+ overrides: Partial = {}
+): SearchHit => ({
+ _id: 'host.name:not-found',
+ _index: '.asset-criticality-default',
+ _source: {
+ '@timestamp': '2021-09-16T15:00:00.000Z',
+ id_field: 'host.name',
+ id_value: 'hostname',
+ criticality_level: 'normal',
+ ...overrides,
+ },
+});
+
+describe('AssetCriticalityService', () => {
+ describe('#getCriticalitiesByIdentifiers()', () => {
+ let baseIdentifier: { id_field: string; id_value: string };
+ let mockAssetCriticalityDataClient: AssetCriticalityDataClient;
+ let service: AssetCriticalityService;
+
+ beforeEach(() => {
+ mockAssetCriticalityDataClient = assetCriticalityDataClientMock.create();
+ baseIdentifier = { id_field: 'host.name', id_value: 'not-found' };
+
+ (mockAssetCriticalityDataClient.search as jest.Mock).mockResolvedValueOnce({
+ hits: { hits: [] },
+ });
+ service = assetCriticalityServiceFactory({
+ assetCriticalityDataClient: mockAssetCriticalityDataClient,
+ experimentalFeatures: {} as ExperimentalFeatures,
+ });
+ });
+
+ describe('specifying a single identifier', () => {
+ it('returns an empty response if identifier is not found', async () => {
+ const result = await service.getCriticalitiesByIdentifiers([baseIdentifier]);
+
+ expect(result).toEqual([]);
+ });
+
+ it('returns a single criticality if identifier is found', async () => {
+ const hits = [buildMockCriticalityHit()];
+ (mockAssetCriticalityDataClient.search as jest.Mock).mockReset().mockResolvedValueOnce({
+ hits: { hits },
+ });
+
+ const result = await service.getCriticalitiesByIdentifiers([baseIdentifier]);
+
+ expect(result).toEqual(hits.map((hit) => hit._source));
+ });
+ });
+
+ describe('specifying multiple identifiers', () => {
+ it('returns an empty response if identifier is not found', async () => {
+ const result = await service.getCriticalitiesByIdentifiers([baseIdentifier]);
+
+ expect(result).toEqual([]);
+ });
+
+ it('generates a single terms clause for multiple identifier values on the same field', async () => {
+ const multipleIdentifiers = [
+ { id_field: 'user.name', id_value: 'one' },
+ { id_field: 'user.name', id_value: 'other' },
+ ];
+
+ await service.getCriticalitiesByIdentifiers(multipleIdentifiers);
+
+ expect(mockAssetCriticalityDataClient.search).toHaveBeenCalledTimes(1);
+ const query = (mockAssetCriticalityDataClient.search as jest.Mock).mock.calls[0][0].query;
+ expect(query).toMatchObject({
+ bool: {
+ filter: {
+ bool: {
+ should: [
+ {
+ bool: {
+ must: [
+ { term: { id_field: 'user.name' } },
+ { terms: { id_value: ['one', 'other'] } },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ });
+
+ it('deduplicates identifiers', async () => {
+ const duplicateIdentifiers = [
+ { id_field: 'user.name', id_value: 'same' },
+ { id_field: 'user.name', id_value: 'same' },
+ ];
+ await service.getCriticalitiesByIdentifiers(duplicateIdentifiers);
+
+ expect(mockAssetCriticalityDataClient.search).toHaveBeenCalledTimes(1);
+ const query = (mockAssetCriticalityDataClient.search as jest.Mock).mock.calls[0][0].query;
+ expect(query).toMatchObject({
+ bool: {
+ filter: {
+ bool: {
+ should: [
+ {
+ bool: {
+ must: [
+ { term: { id_field: 'user.name' } },
+ { terms: { id_value: ['same'] } },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+ });
+ });
+
+ it('returns multiple criticalities if identifiers are found', async () => {
+ const hits = [
+ buildMockCriticalityHit(),
+ buildMockCriticalityHit({
+ id_field: 'user.name',
+ id_value: 'username',
+ criticality_level: 'very_important',
+ }),
+ ];
+
+ (mockAssetCriticalityDataClient.search as jest.Mock).mockReset().mockResolvedValueOnce({
+ hits: {
+ hits,
+ },
+ });
+ const result = await service.getCriticalitiesByIdentifiers([baseIdentifier]);
+ expect(result).toEqual(hits.map((hit) => hit._source));
+ });
+ });
+
+ describe('arguments', () => {
+ it('accepts a single identifier as an array', async () => {
+ const identifier = { id_field: 'host.name', id_value: 'foo' };
+
+ expect(() => service.getCriticalitiesByIdentifiers([identifier])).not.toThrow();
+ });
+
+ it('accepts multiple identifiers', async () => {
+ const identifiers = [
+ { id_field: 'host.name', id_value: 'foo' },
+ { id_field: 'user.name', id_value: 'bar' },
+ ];
+ expect(() => service.getCriticalitiesByIdentifiers(identifiers)).not.toThrow();
+ });
+
+ it('throws an error if an empty array is provided', async () => {
+ await expect(() => service.getCriticalitiesByIdentifiers([])).rejects.toThrowError(
+ 'At least one identifier must be provided'
+ );
+ });
+
+ it('throws an error if no identifier values are provided', async () => {
+ await expect(() =>
+ service.getCriticalitiesByIdentifiers([{ id_field: 'host.name', id_value: '' }])
+ ).rejects.toThrowError('At least one identifier must contain a valid field and value');
+ });
+
+ it('throws an error if no valid identifier field/value pair is provided', async () => {
+ const identifiers = [
+ { id_field: '', id_value: 'foo' },
+ { id_field: 'user.name', id_value: '' },
+ ];
+ await expect(() => service.getCriticalitiesByIdentifiers(identifiers)).rejects.toThrowError(
+ 'At least one identifier must contain a valid field and value'
+ );
+ });
+ });
+
+ describe('error conditions', () => {
+ it('throws an error if the client does', async () => {
+ (mockAssetCriticalityDataClient.search as jest.Mock)
+ .mockReset()
+ .mockRejectedValueOnce(new Error('foo'));
+ await expect(() =>
+ service.getCriticalitiesByIdentifiers([baseIdentifier])
+ ).rejects.toThrowError('foo');
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.ts
new file mode 100644
index 0000000000000..f5320ea396008
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_service.ts
@@ -0,0 +1,101 @@
+/*
+ * 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 type { ExperimentalFeatures } from '../../../../common';
+import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics';
+import type { AssetCriticalityDataClient } from './asset_criticality_data_client';
+
+interface CriticalityIdentifier {
+ id_field: string;
+ id_value: string;
+}
+
+interface IdentifierValuesByField {
+ [idField: string]: string[];
+}
+
+export interface AssetCriticalityService {
+ getCriticalitiesByIdentifiers: (
+ identifiers: CriticalityIdentifier[]
+ ) => Promise;
+ isEnabled: () => boolean;
+}
+
+const isCriticalityIdentifierValid = (identifier: CriticalityIdentifier): boolean =>
+ !isEmpty(identifier.id_field) && !isEmpty(identifier.id_value);
+
+const groupIdentifierValuesByField = (
+ identifiers: CriticalityIdentifier[]
+): IdentifierValuesByField =>
+ identifiers.reduce((acc, id) => {
+ acc[id.id_field] ??= [];
+ if (!acc[id.id_field].includes(id.id_value)) {
+ acc[id.id_field].push(id.id_value);
+ }
+ return acc;
+ }, {} as IdentifierValuesByField);
+
+const buildCriticalitiesQuery = (identifierValuesByField: IdentifierValuesByField) => ({
+ bool: {
+ filter: {
+ bool: {
+ should: Object.keys(identifierValuesByField).map((idField) => ({
+ bool: {
+ must: [
+ { term: { id_field: idField } },
+ { terms: { id_value: identifierValuesByField[idField] } },
+ ],
+ },
+ })),
+ },
+ },
+ },
+});
+
+const getCriticalitiesByIdentifiers = async ({
+ assetCriticalityDataClient,
+ identifiers,
+}: {
+ assetCriticalityDataClient: AssetCriticalityDataClient;
+ identifiers: CriticalityIdentifier[];
+}): Promise => {
+ if (identifiers.length === 0) {
+ throw new Error('At least one identifier must be provided');
+ }
+ const validIdentifiers = identifiers.filter((id) => isCriticalityIdentifierValid(id));
+
+ if (validIdentifiers.length === 0) {
+ throw new Error('At least one identifier must contain a valid field and value');
+ }
+
+ const identifierCount = validIdentifiers.length;
+ const identifierValuesByField = groupIdentifierValuesByField(validIdentifiers);
+ const criticalitiesQuery = buildCriticalitiesQuery(identifierValuesByField);
+
+ const criticalitySearchResponse = await assetCriticalityDataClient.search({
+ query: criticalitiesQuery,
+ size: identifierCount,
+ });
+
+ // @ts-expect-error @elastic/elasticsearch _source is optional
+ return criticalitySearchResponse.hits.hits.map((hit) => hit._source);
+};
+
+interface AssetCriticalityServiceFactoryOptions {
+ assetCriticalityDataClient: AssetCriticalityDataClient;
+ experimentalFeatures: ExperimentalFeatures;
+}
+
+export const assetCriticalityServiceFactory = ({
+ assetCriticalityDataClient,
+ experimentalFeatures,
+}: AssetCriticalityServiceFactoryOptions): AssetCriticalityService => ({
+ getCriticalitiesByIdentifiers: (identifiers: CriticalityIdentifier[]) =>
+ getCriticalitiesByIdentifiers({ assetCriticalityDataClient, identifiers }),
+ isEnabled: () => experimentalFeatures.entityAnalyticsAssetCriticalityEnabled,
+});
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/configurations.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/constants.ts
similarity index 67%
rename from x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/configurations.ts
rename to x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/constants.ts
index c1b309c3a2f44..536230b865a8a 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/configurations.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/constants.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
import type { FieldMap } from '@kbn/alerts-as-data-utils';
+import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics';
export const assetCriticalityFieldMap: FieldMap = {
'@timestamp': {
@@ -33,3 +34,13 @@ export const assetCriticalityFieldMap: FieldMap = {
required: false,
},
} as const;
+
+/**
+ * CriticalityModifiers are used to adjust the risk score based on the criticality of the asset.
+ */
+export const CriticalityModifiers: Record = {
+ very_important: 2,
+ important: 1.5,
+ normal: 1,
+ not_important: 0.5,
+};
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.test.ts
new file mode 100644
index 0000000000000..9ec395223c937
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.test.ts
@@ -0,0 +1,102 @@
+/*
+ * 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 { applyCriticalityToScore, normalize } from './helpers';
+
+describe('applyCriticalityToScore', () => {
+ describe('integer scores', () => {
+ it('returns the original score if the modifier is undefined', () => {
+ const result = applyCriticalityToScore({ modifier: undefined, score: 90 });
+ expect(result).toEqual(90);
+ });
+
+ it('returns the original score if the modifier is 1', () => {
+ const result = applyCriticalityToScore({ modifier: 1, score: 90 });
+ expect(result).toEqual(90);
+ });
+
+ it('returns an increased score if the modifier is greater than 1', () => {
+ const result = applyCriticalityToScore({ modifier: 1.5, score: 90 });
+ expect(result).toEqual(93.10344827586206);
+ });
+
+ it('returns a decreased score if the modifier is less than 1', () => {
+ const result = applyCriticalityToScore({ modifier: 0.5, score: 90 });
+ expect(result).toEqual(81.81818181818181);
+ });
+
+ it('does not exceed a score of 100 with a previous score of 99 and a large modifier', () => {
+ const result = applyCriticalityToScore({ modifier: 200, score: 99 });
+ expect(result).toEqual(99.99494975001262);
+ });
+ });
+
+ describe('non-integer scores', () => {
+ it('returns the original score if the modifier is undefined', () => {
+ const result = applyCriticalityToScore({ modifier: undefined, score: 90.5 });
+ expect(result).toEqual(90.5);
+ });
+
+ it('returns the original score if the modifier is 1', () => {
+ const result = applyCriticalityToScore({ modifier: 1, score: 91.84 });
+ expect(result).toEqual(91.84);
+ });
+ it('returns an increased score if the modifier is greater than 1', () => {
+ const result = applyCriticalityToScore({ modifier: 1.5, score: 75.98 });
+ expect(result).toEqual(82.59294151750127);
+ });
+
+ it('returns a decreased score if the modifier is less than 1', () => {
+ const result = applyCriticalityToScore({ modifier: 0.5, score: 44.12 });
+ expect(result).toEqual(28.303823453938925);
+ });
+
+ it('does not exceed a score of 100 with a high previous score and a large modifier', () => {
+ const result = applyCriticalityToScore({ modifier: 200, score: 99.88 });
+ expect(result).toEqual(99.9993992827436);
+ });
+ });
+});
+
+describe('normalize', () => {
+ it('returns 0 if the number is equal to the min', () => {
+ const result = normalize({ number: 0, min: 0, max: 100 });
+ expect(result).toEqual(0);
+ });
+
+ it('returns 100 if the number is equal to the max', () => {
+ const result = normalize({ number: 100, min: 0, max: 100 });
+ expect(result).toEqual(100);
+ });
+
+ it('returns 50 if the number is halfway between the min and max', () => {
+ const result = normalize({ number: 50, min: 0, max: 100 });
+ expect(result).toEqual(50);
+ });
+
+ it('defaults to a min of 0', () => {
+ const result = normalize({ number: 50, max: 100 });
+ expect(result).toEqual(50);
+ });
+
+ describe('when the domain is diffrent than the range', () => {
+ it('returns 0 if the number is equal to the min', () => {
+ const result = normalize({ number: 20, min: 20, max: 200 });
+ expect(result).toEqual(0);
+ });
+
+ it('returns 100 if the number is equal to the max', () => {
+ const result = normalize({ number: 40, min: 30, max: 40 });
+ expect(result).toEqual(100);
+ });
+
+ it('returns 50 if the number is halfway between the min and max', () => {
+ const result = normalize({ number: 20, min: 0, max: 40 });
+ expect(result).toEqual(50);
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.ts
new file mode 100644
index 0000000000000..3c12fbc89b827
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.ts
@@ -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 type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics';
+import { RISK_SCORING_NORMALIZATION_MAX } from '../risk_score/constants';
+import { CriticalityModifiers } from './constants';
+
+/**
+ * Retrieves the criticality modifier for a given criticality level.
+ *
+ * @param criticalityLevel The criticality level for which to get the modifier.
+ * @returns The associated criticality modifier for the given criticality level.
+ */
+export const getCriticalityModifier = (
+ criticalityLevel?: AssetCriticalityRecord['criticality_level']
+): number | undefined => {
+ if (criticalityLevel == null) {
+ return;
+ }
+
+ return CriticalityModifiers[criticalityLevel];
+};
+
+/**
+ * Applies asset criticality to a normalized risk score using bayesian inference.
+ * @param modifier - The criticality modifier to apply to the score.
+ * @param score - The normalized risk score to which the criticality modifier is applied
+ *
+ * @returns The risk score with the criticality modifier applied.
+ */
+export const applyCriticalityToScore = ({
+ modifier,
+ score,
+}: {
+ modifier: number | undefined;
+ score: number;
+}): number => {
+ if (modifier == null) {
+ return score;
+ }
+
+ return bayesianUpdate({ max: RISK_SCORING_NORMALIZATION_MAX, modifier, score });
+};
+
+/**
+ * Updates a score with the given modifier using bayesian inference.
+ * @param modifier - The modifier to be applied to the score.
+ * @param score - The score to modifiers are applied
+ * @param max - The maximum value of the score.
+ *
+ * @returns The updated score with modifiers applied
+ */
+export const bayesianUpdate = ({
+ max,
+ modifier,
+ score,
+}: {
+ max: number;
+ modifier: number;
+ score: number;
+}) => {
+ const priorProbability = score / (max - score);
+ const newProbability = priorProbability * modifier;
+ return (max * newProbability) / (1 + newProbability);
+};
+
+/**
+ * Normalizes a number to the range [0, 100]
+ *
+ * @param number - The number to be normalized
+ * @param min - The minimum possible value of the number. Defaults to 0.
+ * @param max - The maximum possible value of the number
+ *
+ * @returns The updated score with modifiers applied
+ */
+export const normalize = ({
+ number,
+ min = 0,
+ max,
+}: {
+ number: number;
+ min?: number;
+ max: number;
+}) => ((number - min) / (max - min)) * 100;
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/index.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/index.ts
new file mode 100644
index 0000000000000..7b8b38945dae5
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/index.ts
@@ -0,0 +1,9 @@
+/*
+ * 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 * from './asset_criticality_service';
+export * from './asset_criticality_data_client';
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_and_persist_risk_scores.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_and_persist_risk_scores.ts
index 77258d313034c..25bc1bddd99fa 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_and_persist_risk_scores.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_and_persist_risk_scores.ts
@@ -9,10 +9,12 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server';
import type { RiskScoreDataClient } from './risk_score_data_client';
import type { CalculateAndPersistScoresParams, CalculateAndPersistScoresResponse } from '../types';
+import type { AssetCriticalityService } from '../asset_criticality/asset_criticality_service';
import { calculateRiskScores } from './calculate_risk_scores';
export const calculateAndPersistRiskScores = async (
params: CalculateAndPersistScoresParams & {
+ assetCriticalityService: AssetCriticalityService;
esClient: ElasticsearchClient;
logger: Logger;
spaceId: string;
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.mock.ts
index 20db1f8d61735..fbfaaf11140d8 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.mock.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.mock.ts
@@ -23,7 +23,6 @@ const buildRiskScoreBucketMock = (overrides: Partial = {}): Ris
value: {
score: 20,
normalized_score: 30.0,
- level: 'Unknown',
notes: [],
category_1_score: 30,
category_1_count: 1,
@@ -88,11 +87,15 @@ const buildResponseMock = (
'@timestamp': '2021-08-19T20:55:59.000Z',
id_field: 'host.name',
id_value: 'hostname',
+ criticality_level: 'important',
+ criticality_modifier: 1.5,
calculated_level: 'Unknown',
calculated_score: 20,
calculated_score_norm: 30,
category_1_score: 30,
category_1_count: 12,
+ category_2_score: 0,
+ category_2_count: 0,
notes: [],
inputs: [
{
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.test.ts
index f75fd94c42d30..30d3d2f0566ee 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.test.ts
@@ -7,6 +7,7 @@
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks';
+import { assetCriticalityServiceMock } from '../asset_criticality/asset_criticality_service.mock';
import { calculateRiskScores } from './calculate_risk_scores';
import { calculateRiskScoresMock } from './calculate_risk_scores.mock';
@@ -21,6 +22,7 @@ describe('calculateRiskScores()', () => {
logger = loggingSystemMock.createLogger();
params = {
afterKeys: {},
+ assetCriticalityService: assetCriticalityServiceMock.create(),
esClient,
logger,
index: 'index',
@@ -184,7 +186,7 @@ describe('calculateRiskScores()', () => {
'@timestamp': expect.any(String),
id_field: expect.any(String),
id_value: expect.any(String),
- calculated_level: 'Unknown',
+ calculated_level: 'Low',
calculated_score: expect.any(Number),
calculated_score_norm: expect.any(Number),
category_1_score: expect.any(Number),
@@ -217,18 +219,44 @@ describe('calculateRiskScores()', () => {
});
describe('error conditions', () => {
- beforeEach(() => {
- // stub out a rejected response
+ it('raises an error if elasticsearch client rejects', async () => {
(esClient.search as jest.Mock).mockRejectedValueOnce({
aggregations: calculateRiskScoresMock.buildAggregationResponse(),
});
- });
- it('raises an error if elasticsearch client rejects', () => {
- expect.assertions(1);
- expect(() => calculateRiskScores(params)).rejects.toEqual({
+ await expect(() => calculateRiskScores(params)).rejects.toEqual({
aggregations: calculateRiskScoresMock.buildAggregationResponse(),
});
});
+
+ describe('when the asset criticality service throws an error', () => {
+ beforeEach(() => {
+ (esClient.search as jest.Mock).mockResolvedValueOnce({
+ aggregations: calculateRiskScoresMock.buildAggregationResponse(),
+ });
+ (
+ params.assetCriticalityService.getCriticalitiesByIdentifiers as jest.Mock
+ ).mockRejectedValueOnce(new Error('foo'));
+ });
+
+ it('logs the error but proceeds if asset criticality service throws', async () => {
+ await expect(calculateRiskScores(params)).resolves.toEqual(
+ expect.objectContaining({
+ scores: expect.objectContaining({
+ host: expect.arrayContaining([
+ expect.objectContaining({
+ calculated_level: expect.any(String),
+ id_field: expect.any(String),
+ id_value: expect.any(String),
+ }),
+ ]),
+ }),
+ })
+ );
+ expect(logger.warn).toHaveBeenCalledWith(
+ 'Error retrieving criticality: Error: foo. Scoring will proceed without criticality information.'
+ );
+ });
+ });
});
});
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts
index a877bfd0f8e60..d9af3457d6915 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/calculate_risk_scores.ts
@@ -17,14 +17,22 @@ import {
ALERT_WORKFLOW_STATUS,
EVENT_KIND,
} from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names';
-import type {
- AfterKeys,
- IdentifierType,
- RiskWeights,
- RiskScore,
+import {
+ type AfterKeys,
+ type IdentifierType,
+ type RiskWeights,
+ type RiskScore,
+ getRiskLevel,
+ RiskCategories,
} from '../../../../common/entity_analytics/risk_engine';
-import { RiskCategories } from '../../../../common/entity_analytics/risk_engine';
import { withSecuritySpan } from '../../../utils/with_security_span';
+import type { AssetCriticalityRecord } from '../../../../common/api/entity_analytics';
+import type { AssetCriticalityService } from '../asset_criticality/asset_criticality_service';
+import {
+ applyCriticalityToScore,
+ getCriticalityModifier,
+ normalize,
+} from '../asset_criticality/helpers';
import { getAfterKeyForIdentifierType, getFieldForIdentifierAgg } from './helpers';
import {
buildCategoryCountDeclarations,
@@ -39,34 +47,68 @@ import type {
CalculateScoresResponse,
RiskScoreBucket,
} from '../types';
+import {
+ RISK_SCORING_INPUTS_COUNT_MAX,
+ RISK_SCORING_SUM_MAX,
+ RISK_SCORING_SUM_VALUE,
+} from './constants';
-const bucketToResponse = ({
+const formatForResponse = ({
bucket,
+ criticality,
now,
identifierField,
+ includeNewFields,
}: {
bucket: RiskScoreBucket;
+ criticality?: AssetCriticalityRecord;
now: string;
identifierField: string;
-}): RiskScore => ({
- '@timestamp': now,
- id_field: identifierField,
- id_value: bucket.key[identifierField],
- calculated_level: bucket.risk_details.value.level,
- calculated_score: bucket.risk_details.value.score,
- calculated_score_norm: bucket.risk_details.value.normalized_score,
- category_1_score: bucket.risk_details.value.category_1_score,
- category_1_count: bucket.risk_details.value.category_1_count,
- notes: bucket.risk_details.value.notes,
- inputs: bucket.inputs.hits.hits.map((riskInput) => ({
- id: riskInput._id,
- index: riskInput._index,
- description: `Alert from Rule: ${riskInput.fields?.[ALERT_RULE_NAME]?.[0] ?? 'RULE_NOT_FOUND'}`,
- category: RiskCategories.category_1,
- risk_score: riskInput.fields?.[ALERT_RISK_SCORE]?.[0] ?? undefined,
- timestamp: riskInput.fields?.['@timestamp']?.[0] ?? undefined,
- })),
-});
+ includeNewFields: boolean;
+}): RiskScore => {
+ const criticalityModifier = getCriticalityModifier(criticality?.criticality_level);
+ const normalizedScoreWithCriticality = applyCriticalityToScore({
+ score: bucket.risk_details.value.normalized_score,
+ modifier: criticalityModifier,
+ });
+ const calculatedLevel = getRiskLevel(normalizedScoreWithCriticality);
+ const categoryTwoScore =
+ normalizedScoreWithCriticality - bucket.risk_details.value.normalized_score;
+ const categoryTwoCount = criticalityModifier ? 1 : 0;
+
+ const newFields = {
+ category_2_score: categoryTwoScore,
+ category_2_count: categoryTwoCount,
+ criticality_level: criticality?.criticality_level,
+ criticality_modifier: criticalityModifier,
+ };
+
+ return {
+ '@timestamp': now,
+ id_field: identifierField,
+ id_value: bucket.key[identifierField],
+ calculated_level: calculatedLevel,
+ calculated_score: bucket.risk_details.value.score,
+ calculated_score_norm: normalizedScoreWithCriticality,
+ category_1_score: normalize({
+ number: bucket.risk_details.value.category_1_score,
+ max: RISK_SCORING_SUM_MAX,
+ }),
+ category_1_count: bucket.risk_details.value.category_1_count,
+ notes: bucket.risk_details.value.notes,
+ inputs: bucket.inputs.hits.hits.map((riskInput) => ({
+ id: riskInput._id,
+ index: riskInput._index,
+ description: `Alert from Rule: ${
+ riskInput.fields?.[ALERT_RULE_NAME]?.[0] ?? 'RULE_NOT_FOUND'
+ }`,
+ category: RiskCategories.category_1,
+ risk_score: riskInput.fields?.[ALERT_RISK_SCORE]?.[0] ?? undefined,
+ timestamp: riskInput.fields?.['@timestamp']?.[0] ?? undefined,
+ })),
+ ...(includeNewFields ? newFields : {}),
+ };
+};
const filterFromRange = (range: CalculateScoresParams['range']): QueryDslQueryContainer => ({
range: { '@timestamp': { lt: range.end, gte: range.start } },
@@ -108,22 +150,6 @@ const buildReduceScript = ({
results['score'] = total_score;
results['normalized_score'] = score_norm;
- if (score_norm < 20) {
- results['level'] = 'Unknown'
- }
- else if (score_norm >= 20 && score_norm < 40) {
- results['level'] = 'Low'
- }
- else if (score_norm >= 40 && score_norm < 70) {
- results['level'] = 'Moderate'
- }
- else if (score_norm >= 70 && score_norm < 90) {
- results['level'] = 'High'
- }
- else if (score_norm >= 90) {
- results['level'] = 'Critical'
- }
-
return results;
`;
};
@@ -184,9 +210,9 @@ const buildIdentifierTypeAggregation = ({
`,
combine_script: 'return state;',
params: {
- max_risk_inputs_per_identity: 999999,
- p: 1.5,
- risk_cap: 261.2,
+ max_risk_inputs_per_identity: RISK_SCORING_INPUTS_COUNT_MAX,
+ p: RISK_SCORING_SUM_VALUE,
+ risk_cap: RISK_SCORING_SUM_MAX,
},
reduce_script: buildReduceScript({ globalIdentifierTypeWeight }),
},
@@ -195,8 +221,55 @@ const buildIdentifierTypeAggregation = ({
};
};
+const processScores = async ({
+ assetCriticalityService,
+ buckets,
+ identifierField,
+ logger,
+ now,
+}: {
+ assetCriticalityService: AssetCriticalityService;
+ buckets: RiskScoreBucket[];
+ identifierField: string;
+ logger: Logger;
+ now: string;
+}): Promise => {
+ if (buckets.length === 0) {
+ return [];
+ }
+
+ if (!assetCriticalityService.isEnabled()) {
+ return buckets.map((bucket) =>
+ formatForResponse({ bucket, now, identifierField, includeNewFields: false })
+ );
+ }
+
+ const identifiers = buckets.map((bucket) => ({
+ id_field: identifierField,
+ id_value: bucket.key[identifierField],
+ }));
+
+ let criticalities: AssetCriticalityRecord[] = [];
+ try {
+ criticalities = await assetCriticalityService.getCriticalitiesByIdentifiers(identifiers);
+ } catch (e) {
+ logger.warn(
+ `Error retrieving criticality: ${e}. Scoring will proceed without criticality information.`
+ );
+ }
+
+ return buckets.map((bucket) => {
+ const criticality = criticalities.find(
+ (c) => c.id_field === identifierField && c.id_value === bucket.key[identifierField]
+ );
+
+ return formatForResponse({ bucket, criticality, identifierField, now, includeNewFields: true });
+ });
+};
+
export const calculateRiskScores = async ({
afterKeys: userAfterKeys,
+ assetCriticalityService,
debug,
esClient,
filter: userFilter,
@@ -208,6 +281,7 @@ export const calculateRiskScores = async ({
runtimeMappings,
weights,
}: {
+ assetCriticalityService: AssetCriticalityService;
esClient: ElasticsearchClient;
logger: Logger;
} & CalculateScoresParams): Promise =>
@@ -274,16 +348,27 @@ export const calculateRiskScores = async ({
user: response.aggregations.user?.after_key,
};
+ const hostScores = await processScores({
+ assetCriticalityService,
+ buckets: hostBuckets,
+ identifierField: 'host.name',
+ logger,
+ now,
+ });
+ const userScores = await processScores({
+ assetCriticalityService,
+ buckets: userBuckets,
+ identifierField: 'user.name',
+ logger,
+ now,
+ });
+
return {
...(debug ? { request, response } : {}),
after_keys: afterKeys,
scores: {
- host: hostBuckets.map((bucket) =>
- bucketToResponse({ bucket, identifierField: 'host.name', now })
- ),
- user: userBuckets.map((bucket) =>
- bucketToResponse({ bucket, identifierField: 'user.name', now })
- ),
+ host: hostScores,
+ user: userScores,
},
};
});
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/constants.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/constants.ts
new file mode 100644
index 0000000000000..57e67960f96e2
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/constants.ts
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+/**
+ * The risk scoring algorithm uses a Riemann zeta function to sum an entity's risk inputs to a known, finite value (@see RISK_SCORING_SUM_MAX). It does so by assigning each input a weight based on its position in the list (ordered by score) of inputs. This value represents the complex variable s of Re(s) in traditional Riemann zeta function notation.
+ */
+export const RISK_SCORING_SUM_VALUE = 1.5;
+
+/**
+ * Represents the maximum possible risk score sum. This value is derived from RISK_SCORING_SUM_VALUE, but we store the precomputed value here to be used more conveniently in normalization.
+ * @see RISK_SCORING_SUM_VALUE
+ */
+export const RISK_SCORING_SUM_MAX = 261.2;
+
+/**
+ * The risk scoring algorithm can only process a finite number of risk inputs per identity; this value represents the maximum number of inputs that will be processed.
+ */
+export const RISK_SCORING_INPUTS_COUNT_MAX = 999999;
+
+/**
+ * This value represents the maximum possible risk score after normalization.
+ */
+export const RISK_SCORING_NORMALIZATION_MAX = 100;
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_engine_data_writer.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_engine_data_writer.test.ts
index ca2ad354320a6..271d5511e6b75 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_engine_data_writer.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_engine_data_writer.test.ts
@@ -51,6 +51,10 @@ describe('RiskEngineDataWriter', () => {
"calculated_score_norm": 85.332,
"category_1_count": 12,
"category_1_score": 85,
+ "category_2_count": 0,
+ "category_2_score": 0,
+ "criticality_level": "very_important",
+ "criticality_modifier": 2,
"id_field": "host.name",
"id_value": "hostname",
"inputs": Array [],
@@ -73,6 +77,10 @@ describe('RiskEngineDataWriter', () => {
"calculated_score_norm": 85.332,
"category_1_count": 12,
"category_1_score": 85,
+ "category_2_count": 0,
+ "category_2_score": 0,
+ "criticality_level": "very_important",
+ "criticality_modifier": 2,
"id_field": "host.name",
"id_value": "hostname",
"inputs": Array [],
@@ -117,6 +125,10 @@ describe('RiskEngineDataWriter', () => {
"calculated_score_norm": 85.332,
"category_1_count": 12,
"category_1_score": 85,
+ "category_2_count": 0,
+ "category_2_score": 0,
+ "criticality_level": "very_important",
+ "criticality_modifier": 2,
"id_field": "user.name",
"id_value": "username_1",
"inputs": Array [],
@@ -139,6 +151,10 @@ describe('RiskEngineDataWriter', () => {
"calculated_score_norm": 85.332,
"category_1_count": 12,
"category_1_score": 85,
+ "category_2_count": 0,
+ "category_2_score": 0,
+ "criticality_level": "very_important",
+ "criticality_modifier": 2,
"id_field": "user.name",
"id_value": "username_2",
"inputs": Array [],
@@ -189,6 +205,10 @@ describe('RiskEngineDataWriter', () => {
"calculated_score_norm": 85.332,
"category_1_count": 12,
"category_1_score": 85,
+ "category_2_count": 0,
+ "category_2_score": 0,
+ "criticality_level": "very_important",
+ "criticality_modifier": 2,
"id_field": "host.name",
"id_value": "hostname_1",
"inputs": Array [],
@@ -211,6 +231,10 @@ describe('RiskEngineDataWriter', () => {
"calculated_score_norm": 85.332,
"category_1_count": 12,
"category_1_score": 85,
+ "category_2_count": 0,
+ "category_2_score": 0,
+ "criticality_level": "very_important",
+ "criticality_modifier": 2,
"id_field": "user.name",
"id_value": "username_1",
"inputs": Array [],
@@ -233,6 +257,10 @@ describe('RiskEngineDataWriter', () => {
"calculated_score_norm": 85.332,
"category_1_count": 12,
"category_1_score": 85,
+ "category_2_count": 0,
+ "category_2_score": 0,
+ "criticality_level": "very_important",
+ "criticality_modifier": 2,
"id_field": "user.name",
"id_value": "username_2",
"inputs": Array [],
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_score_service.mock.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_score_service.mock.ts
index e72852f6ea47a..5353d38fcaefa 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_score_service.mock.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_score_service.mock.ts
@@ -17,6 +17,10 @@ const createRiskScoreMock = (overrides: Partial = {}): RiskScore => (
calculated_score_norm: 85.332,
category_1_score: 85,
category_1_count: 12,
+ category_2_count: 0,
+ category_2_score: 0,
+ criticality_level: 'very_important',
+ criticality_modifier: 2,
notes: [],
inputs: [],
...overrides,
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_score_service.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_score_service.ts
index a89835c6c7327..c62e4c3ead828 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_score_service.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_score_service.ts
@@ -16,6 +16,7 @@ import type {
import { calculateRiskScores } from './calculate_risk_scores';
import { calculateAndPersistRiskScores } from './calculate_and_persist_risk_scores';
import type { RiskEngineDataClient } from '../risk_engine/risk_engine_data_client';
+import type { AssetCriticalityService } from '../asset_criticality/asset_criticality_service';
import type { RiskScoreDataClient } from './risk_score_data_client';
import type { RiskInputsIndexResponse } from './get_risk_inputs_index';
import { scheduleLatestTransformNow } from '../utils/transforms';
@@ -31,6 +32,7 @@ export interface RiskScoreService {
}
export interface RiskScoreServiceFactoryParams {
+ assetCriticalityService: AssetCriticalityService;
esClient: ElasticsearchClient;
logger: Logger;
riskEngineDataClient: RiskEngineDataClient;
@@ -39,15 +41,24 @@ export interface RiskScoreServiceFactoryParams {
}
export const riskScoreServiceFactory = ({
+ assetCriticalityService,
esClient,
logger,
riskEngineDataClient,
riskScoreDataClient,
spaceId,
}: RiskScoreServiceFactoryParams): RiskScoreService => ({
- calculateScores: (params) => calculateRiskScores({ ...params, esClient, logger }),
+ calculateScores: (params) =>
+ calculateRiskScores({ ...params, assetCriticalityService, esClient, logger }),
calculateAndPersistScores: (params) =>
- calculateAndPersistRiskScores({ ...params, esClient, logger, riskScoreDataClient, spaceId }),
+ calculateAndPersistRiskScores({
+ ...params,
+ assetCriticalityService,
+ esClient,
+ logger,
+ riskScoreDataClient,
+ spaceId,
+ }),
getConfiguration: async () => riskEngineDataClient.getConfiguration(),
getRiskInputsIndex: async (params) => riskScoreDataClient.getRiskInputsIndex(params),
scheduleLatestTransformNow: () => scheduleLatestTransformNow({ namespace: spaceId, esClient }),
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_weights.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_weights.ts
index f0af4360b8631..de1754ba3de21 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_weights.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/risk_weights.ts
@@ -54,7 +54,7 @@ const getWeightForIdentifierType = (weight: RiskWeight, identifierType: Identifi
};
export const buildCategoryScoreDeclarations = (): string => {
- return RISK_CATEGORIES.map((riskCategory) => `results['${riskCategory}_score'] = 0;`).join('');
+ return RISK_CATEGORIES.map((riskCategory) => `results['${riskCategory}_score'] = 0.0;`).join('');
};
export const buildCategoryCountDeclarations = (): string => {
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.test.ts
index 0a62695dfd680..fb5c366193cb7 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.test.ts
@@ -9,6 +9,7 @@ import { riskScoreCalculationRoute } from './calculation';
import { loggerMock } from '@kbn/logging-mocks';
+import type { ExperimentalFeatures } from '../../../../../common';
import { RISK_SCORE_CALCULATION_URL } from '../../../../../common/constants';
import {
serverMock,
@@ -44,7 +45,7 @@ describe('risk score calculation route', () => {
clients.appClient.getAlertsIndex.mockReturnValue('default-alerts-index');
(riskScoreServiceFactory as jest.Mock).mockReturnValue(mockRiskScoreService);
- riskScoreCalculationRoute(server.router, logger);
+ riskScoreCalculationRoute(server.router, logger, {} as ExperimentalFeatures);
});
const buildRequest = (overrides: object = {}) => {
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.ts
index 1822c038b7d1d..71f4d05712d68 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/calculation.ts
@@ -14,12 +14,18 @@ import {
RISK_SCORE_CALCULATION_URL,
} from '../../../../../common/constants';
import { riskScoreCalculationRequestSchema } from '../../../../../common/entity_analytics/risk_engine/risk_score_calculation/request_schema';
+import type { ExperimentalFeatures } from '../../../../../common';
import type { SecuritySolutionPluginRouter } from '../../../../types';
import { buildRouteValidation } from '../../../../utils/build_validation/route_validation';
+import { assetCriticalityServiceFactory } from '../../asset_criticality';
import { riskScoreServiceFactory } from '../risk_score_service';
import { getRiskInputsIndex } from '../get_risk_inputs_index';
-export const riskScoreCalculationRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => {
+export const riskScoreCalculationRoute = (
+ router: SecuritySolutionPluginRouter,
+ logger: Logger,
+ experimentalFeatures: ExperimentalFeatures
+) => {
router.versioned
.post({
path: RISK_SCORE_CALCULATION_URL,
@@ -42,8 +48,14 @@ export const riskScoreCalculationRoute = (router: SecuritySolutionPluginRouter,
const spaceId = securityContext.getSpaceId();
const riskEngineDataClient = securityContext.getRiskEngineDataClient();
const riskScoreDataClient = securityContext.getRiskScoreDataClient();
+ const assetCriticalityDataClient = securityContext.getAssetCriticalityDataClient();
+ const assetCriticalityService = assetCriticalityServiceFactory({
+ assetCriticalityDataClient,
+ experimentalFeatures,
+ });
const riskScoreService = riskScoreServiceFactory({
+ assetCriticalityService,
esClient,
logger,
riskEngineDataClient,
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.test.ts
index 9a525a5bae0d5..71d108058a10d 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.test.ts
@@ -7,6 +7,7 @@
import { loggerMock } from '@kbn/logging-mocks';
+import type { ExperimentalFeatures } from '../../../../../common';
import { RISK_SCORE_PREVIEW_URL } from '../../../../../common/constants';
import {
RiskCategories,
@@ -48,7 +49,7 @@ describe('POST risk_engine/preview route', () => {
clients.appClient.getAlertsIndex.mockReturnValue('default-alerts-index');
(riskScoreServiceFactory as jest.Mock).mockReturnValue(mockRiskScoreService);
- riskScorePreviewRoute(server.router, logger);
+ riskScorePreviewRoute(server.router, logger, {} as ExperimentalFeatures);
});
const buildRequest = (body: object = {}) =>
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts
index 13f3ee8a9df07..db11cf84e9049 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/routes/preview.ts
@@ -15,12 +15,18 @@ import {
RISK_SCORE_PREVIEW_URL,
} from '../../../../../common/constants';
import { riskScorePreviewRequestSchema } from '../../../../../common/entity_analytics/risk_engine/risk_score_preview/request_schema';
+import type { ExperimentalFeatures } from '../../../../../common';
import type { SecuritySolutionPluginRouter } from '../../../../types';
import { buildRouteValidation } from '../../../../utils/build_validation/route_validation';
+import { assetCriticalityServiceFactory } from '../../asset_criticality';
import { riskScoreServiceFactory } from '../risk_score_service';
import { getRiskInputsIndex } from '../get_risk_inputs_index';
-export const riskScorePreviewRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => {
+export const riskScorePreviewRoute = (
+ router: SecuritySolutionPluginRouter,
+ logger: Logger,
+ experimentalFeatures: ExperimentalFeatures
+) => {
router.versioned
.post({
access: 'internal',
@@ -43,8 +49,14 @@ export const riskScorePreviewRoute = (router: SecuritySolutionPluginRouter, logg
const spaceId = securityContext.getSpaceId();
const riskEngineDataClient = securityContext.getRiskEngineDataClient();
const riskScoreDataClient = securityContext.getRiskScoreDataClient();
+ const assetCriticalityDataClient = securityContext.getAssetCriticalityDataClient();
+ const assetCriticalityService = assetCriticalityServiceFactory({
+ assetCriticalityDataClient,
+ experimentalFeatures,
+ });
const riskScoreService = riskScoreServiceFactory({
+ assetCriticalityService,
esClient,
logger,
riskEngineDataClient,
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts
index 45562ac8d38a9..bc3d5054980dc 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.test.ts
@@ -11,6 +11,7 @@ import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
import { loggerMock } from '@kbn/logging-mocks';
import type { AnalyticsServiceSetup } from '@kbn/core/public';
+import type { ExperimentalFeatures } from '../../../../../common';
import type { RiskScoreService } from '../risk_score_service';
import { riskScoreServiceMock } from '../risk_score_service.mock';
import { riskScoringTaskMock } from './risk_scoring_task.mock';
@@ -47,6 +48,7 @@ describe('Risk Scoring Task', () => {
it('registers the task with TaskManager', () => {
expect(mockTaskManagerSetup.registerTaskDefinitions).not.toHaveBeenCalled();
registerRiskScoringTask({
+ experimentalFeatures: {} as ExperimentalFeatures,
getStartServices: mockCore.getStartServices,
kibanaVersion: '8.10.0',
taskManager: mockTaskManagerSetup,
@@ -59,6 +61,7 @@ describe('Risk Scoring Task', () => {
it('does nothing if TaskManager is not available', () => {
expect(mockTaskManagerSetup.registerTaskDefinitions).not.toHaveBeenCalled();
registerRiskScoringTask({
+ experimentalFeatures: {} as ExperimentalFeatures,
getStartServices: mockCore.getStartServices,
kibanaVersion: '8.10.0',
taskManager: undefined,
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts
index a1539167bbaf4..421fb4092b928 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/risk_score/tasks/risk_scoring_task.ts
@@ -18,7 +18,11 @@ import type {
TaskManagerStartContract,
} from '@kbn/task-manager-plugin/server';
import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
-import type { AfterKeys, IdentifierType } from '../../../../../common/entity_analytics/risk_engine';
+import {
+ type AfterKeys,
+ type IdentifierType,
+ RiskScoreEntity,
+} from '../../../../../common/entity_analytics/risk_engine';
import type { StartPlugins } from '../../../../plugin';
import { type RiskScoreService, riskScoreServiceFactory } from '../risk_score_service';
import { RiskEngineDataClient } from '../../risk_engine/risk_engine_data_client';
@@ -31,12 +35,16 @@ import {
} from './state';
import { INTERVAL, SCOPE, TIMEOUT, TYPE, VERSION } from './constants';
import { buildScopedInternalSavedObjectsClientUnsafe, convertRangeToISO } from './helpers';
-import { RiskScoreEntity } from '../../../../../common/entity_analytics/risk_engine/types';
+import type { ExperimentalFeatures } from '../../../../../common';
import {
RISK_SCORE_EXECUTION_SUCCESS_EVENT,
RISK_SCORE_EXECUTION_ERROR_EVENT,
RISK_SCORE_EXECUTION_CANCELLATION_EVENT,
} from '../../../telemetry/event_based/events';
+import {
+ AssetCriticalityDataClient,
+ assetCriticalityServiceFactory,
+} from '../../asset_criticality';
const logFactory =
(logger: Logger, taskId: string) =>
@@ -50,12 +58,14 @@ const getTaskId = (namespace: string): string => `${TYPE}:${namespace}:${VERSION
type GetRiskScoreService = (namespace: string) => Promise;
export const registerRiskScoringTask = ({
+ experimentalFeatures,
getStartServices,
kibanaVersion,
logger,
taskManager,
telemetry,
}: {
+ experimentalFeatures: ExperimentalFeatures;
getStartServices: StartServicesAccessor;
kibanaVersion: string;
logger: Logger;
@@ -71,6 +81,17 @@ export const registerRiskScoringTask = ({
getStartServices().then(([coreStart, _]) => {
const esClient = coreStart.elasticsearch.client.asInternalUser;
const soClient = buildScopedInternalSavedObjectsClientUnsafe({ coreStart, namespace });
+
+ const assetCriticalityDataClient = new AssetCriticalityDataClient({
+ esClient,
+ logger,
+ namespace,
+ });
+ const assetCriticalityService = assetCriticalityServiceFactory({
+ assetCriticalityDataClient,
+ experimentalFeatures,
+ });
+
const riskEngineDataClient = new RiskEngineDataClient({
logger,
kibanaVersion,
@@ -87,6 +108,7 @@ export const registerRiskScoringTask = ({
});
return riskScoreServiceFactory({
+ assetCriticalityService,
esClient,
logger,
riskEngineDataClient,
diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/types.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/types.ts
index acdb1982011d2..ea2464b8058a7 100644
--- a/x-pack/plugins/security_solution/server/lib/entity_analytics/types.ts
+++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/types.ts
@@ -117,7 +117,6 @@ export interface RiskScoreBucket {
score: number;
normalized_score: number;
notes: string[];
- level: string;
category_1_score: number;
category_1_count: number;
};
diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts
index 53141fc1751ce..01154dc06f5f6 100644
--- a/x-pack/plugins/security_solution/server/plugin.ts
+++ b/x-pack/plugins/security_solution/server/plugin.ts
@@ -183,6 +183,7 @@ export class Plugin implements ISecuritySolutionPlugin {
if (experimentalFeatures.riskScoringPersistence) {
registerRiskScoringTask({
+ experimentalFeatures,
getStartServices: core.getStartServices,
kibanaVersion: pluginContext.env.packageInfo.version,
logger: this.logger,
diff --git a/x-pack/plugins/security_solution/server/request_context_factory.ts b/x-pack/plugins/security_solution/server/request_context_factory.ts
index 3deaaa103caa6..3f1d9f818e431 100644
--- a/x-pack/plugins/security_solution/server/request_context_factory.ts
+++ b/x-pack/plugins/security_solution/server/request_context_factory.ts
@@ -27,7 +27,7 @@ import type { EndpointAuthz } from '../common/endpoint/types/authz';
import type { EndpointAppContextService } from './endpoint/endpoint_app_context_services';
import { RiskEngineDataClient } from './lib/entity_analytics/risk_engine/risk_engine_data_client';
import { RiskScoreDataClient } from './lib/entity_analytics/risk_score/risk_score_data_client';
-import { AssetCriticalityDataClient } from './lib/entity_analytics/asset_criticality/asset_criticality_data_client';
+import { AssetCriticalityDataClient } from './lib/entity_analytics/asset_criticality';
export interface IRequestContextFactory {
create(
diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts
index ce863fb861076..b32543ad7612d 100644
--- a/x-pack/plugins/security_solution/server/routes/index.ts
+++ b/x-pack/plugins/security_solution/server/routes/index.ts
@@ -159,8 +159,8 @@ export const initRoutes = (
}
if (config.experimentalFeatures.riskScoringRoutesEnabled) {
- riskScorePreviewRoute(router, logger);
- riskScoreCalculationRoute(router, logger);
+ riskScorePreviewRoute(router, logger, config.experimentalFeatures);
+ riskScoreCalculationRoute(router, logger, config.experimentalFeatures);
riskEngineStatusRoute(router);
riskEngineInitRoute(router, getStartServices);
riskEngineEnableRoute(router, getStartServices);
diff --git a/x-pack/plugins/security_solution/server/types.ts b/x-pack/plugins/security_solution/server/types.ts
index a44572ae07eae..7833c1dff6b8a 100644
--- a/x-pack/plugins/security_solution/server/types.ts
+++ b/x-pack/plugins/security_solution/server/types.ts
@@ -31,7 +31,7 @@ import type { EndpointAuthz } from '../common/endpoint/types/authz';
import type { EndpointInternalFleetServicesInterface } from './endpoint/services/fleet';
import type { RiskEngineDataClient } from './lib/entity_analytics/risk_engine/risk_engine_data_client';
import type { RiskScoreDataClient } from './lib/entity_analytics/risk_score/risk_score_data_client';
-import type { AssetCriticalityDataClient } from './lib/entity_analytics/asset_criticality/asset_criticality_data_client';
+import type { AssetCriticalityDataClient } from './lib/entity_analytics/asset_criticality';
export { AppClient };
export interface SecuritySolutionApiRequestHandlerContext {
diff --git a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.test.ts
index cec83d2cf9e71..cb0606594c093 100644
--- a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.test.ts
+++ b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.test.ts
@@ -5,50 +5,16 @@
* 2.0.
*/
-import _ from 'lodash';
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import { RuleExecutorServicesMock, alertsMock } from '@kbn/alerting-plugin/server/mocks';
import sampleAggsJsonResponse from './tests/es_sample_response.json';
import sampleShapesJsonResponse from './tests/es_sample_response_shapes.json';
import { executor } from './executor';
-import type {
- GeoContainmentRuleParams,
- GeoContainmentAlertInstanceState,
- GeoContainmentAlertInstanceContext,
-} from './types';
+import type { GeoContainmentRuleParams, GeoContainmentAlertInstanceContext } from './types';
-const alertFactory = (contextKeys: unknown[], testAlertActionArr: unknown[]) => ({
- create: (instanceId: string) => {
- const alertInstance = alertsMock.createAlertFactory.create<
- GeoContainmentAlertInstanceState,
- GeoContainmentAlertInstanceContext
- >();
- (alertInstance.scheduleActions as jest.Mock).mockImplementation(
- (actionGroupId: string, context?: GeoContainmentAlertInstanceContext) => {
- // Check subset of alert for comparison to expected results
- // @ts-ignore
- const contextSubset = _.pickBy(context, (v, k) => contextKeys.includes(k));
- testAlertActionArr.push({
- actionGroupId,
- instanceId,
- context: contextSubset,
- });
- }
- );
- return alertInstance;
- },
- alertLimit: {
- getValue: () => 1000,
- setLimitReached: () => {},
- },
- done: () => ({ getRecoveredAlerts: () => [] }),
-});
-
-describe('getGeoContainmentExecutor', () => {
- // Params needed for all tests
- const expectedAlertResults = [
+describe('executor', () => {
+ const expectedAlerts = [
{
- actionGroupId: 'Tracked entity contained',
context: {
containingBoundaryId: 'kFATGXkBsFLYN2Tj6AAk',
entityDocumentId: 'ZVBoGXkBsFLYN2Tj1wmV',
@@ -58,7 +24,6 @@ describe('getGeoContainmentExecutor', () => {
instanceId: '0-kFATGXkBsFLYN2Tj6AAk',
},
{
- actionGroupId: 'Tracked entity contained',
context: {
containingBoundaryId: 'kFATGXkBsFLYN2Tj6AAk',
entityDocumentId: 'ZlBoGXkBsFLYN2Tj1wmV',
@@ -68,7 +33,7 @@ describe('getGeoContainmentExecutor', () => {
instanceId: '1-kFATGXkBsFLYN2Tj6AAk',
},
];
- const testAlertActionArr: unknown[] = [];
+
const previousStartedAt = new Date('2021-04-27T16:56:11.923Z');
const startedAt = new Date('2021-04-29T16:56:11.923Z');
const geoContainmentParams: GeoContainmentRuleParams = {
@@ -99,7 +64,6 @@ describe('getGeoContainmentExecutor', () => {
// Boundary test mocks
const boundaryCall = jest.fn();
const esAggCall = jest.fn();
- const contextKeys = Object.keys(expectedAlertResults[0].context);
const esClient = elasticsearchServiceMock.createElasticsearchClient();
// @ts-ignore incomplete return type
esClient.search.mockResponseImplementation(({ index }) => {
@@ -112,10 +76,26 @@ describe('getGeoContainmentExecutor', () => {
}
});
- const alertServicesWithSearchMock: RuleExecutorServicesMock = {
+ const alerts: unknown[] = [];
+ const servicesMock: RuleExecutorServicesMock = {
...alertsMock.createRuleExecutorServices(),
// @ts-ignore
- alertFactory: alertFactory(contextKeys, testAlertActionArr),
+ alertsClient: {
+ getRecoveredAlerts: () => {
+ return [];
+ },
+ report: ({ id, context }: { id: string; context: GeoContainmentAlertInstanceContext }) => {
+ alerts.push({
+ context: {
+ containingBoundaryId: context.containingBoundaryId,
+ entityDocumentId: context.entityDocumentId,
+ entityId: context.entityId,
+ entityLocation: context.entityLocation,
+ },
+ instanceId: id,
+ });
+ },
+ },
// @ts-ignore
scopedClusterClient: {
asCurrentUser: esClient,
@@ -124,7 +104,7 @@ describe('getGeoContainmentExecutor', () => {
beforeEach(() => {
jest.clearAllMocks();
- testAlertActionArr.length = 0;
+ alerts.length = 0;
});
test('should query for shapes if state does not contain shapes', async () => {
@@ -132,7 +112,7 @@ describe('getGeoContainmentExecutor', () => {
previousStartedAt,
startedAt,
// @ts-ignore
- services: alertServicesWithSearchMock,
+ services: servicesMock,
params: geoContainmentParams,
// @ts-ignore
rule: {
@@ -145,7 +125,7 @@ describe('getGeoContainmentExecutor', () => {
expect(boundaryCall.mock.calls.length).toBe(1);
expect(esAggCall.mock.calls.length).toBe(1);
}
- expect(testAlertActionArr).toMatchObject(expectedAlertResults);
+ expect(alerts).toMatchObject(expectedAlerts);
});
test('should query for shapes if boundaries request meta changes', async () => {
@@ -153,7 +133,7 @@ describe('getGeoContainmentExecutor', () => {
previousStartedAt,
startedAt,
// @ts-ignore
- services: alertServicesWithSearchMock,
+ services: servicesMock,
params: geoContainmentParams,
// @ts-ignore
rule: {
@@ -172,7 +152,7 @@ describe('getGeoContainmentExecutor', () => {
expect(boundaryCall.mock.calls.length).toBe(1);
expect(esAggCall.mock.calls.length).toBe(1);
}
- expect(testAlertActionArr).toMatchObject(expectedAlertResults);
+ expect(alerts).toMatchObject(expectedAlerts);
});
test('should not query for shapes if state contains shapes', async () => {
@@ -180,7 +160,7 @@ describe('getGeoContainmentExecutor', () => {
previousStartedAt,
startedAt,
// @ts-ignore
- services: alertServicesWithSearchMock,
+ services: servicesMock,
params: geoContainmentParams,
// @ts-ignore
rule: {
@@ -192,7 +172,7 @@ describe('getGeoContainmentExecutor', () => {
expect(boundaryCall.mock.calls.length).toBe(0);
expect(esAggCall.mock.calls.length).toBe(1);
}
- expect(testAlertActionArr).toMatchObject(expectedAlertResults);
+ expect(alerts).toMatchObject(expectedAlerts);
});
test('should carry through shapes filters in state to next call unmodified', async () => {
@@ -200,7 +180,7 @@ describe('getGeoContainmentExecutor', () => {
previousStartedAt,
startedAt,
// @ts-ignore
- services: alertServicesWithSearchMock,
+ services: servicesMock,
params: geoContainmentParams,
// @ts-ignore
rule: {
@@ -211,7 +191,7 @@ describe('getGeoContainmentExecutor', () => {
if (executionResult && executionResult.state.shapesFilters) {
expect(executionResult.state.shapesFilters).toEqual(geoContainmentState.shapesFilters);
}
- expect(testAlertActionArr).toMatchObject(expectedAlertResults);
+ expect(alerts).toMatchObject(expectedAlerts);
});
test('should return previous locations map', async () => {
@@ -239,7 +219,7 @@ describe('getGeoContainmentExecutor', () => {
previousStartedAt,
startedAt,
// @ts-ignore
- services: alertServicesWithSearchMock,
+ services: servicesMock,
params: geoContainmentParams,
// @ts-ignore
rule: {
@@ -250,6 +230,6 @@ describe('getGeoContainmentExecutor', () => {
if (executionResult && executionResult.state.prevLocationMap) {
expect(executionResult.state.prevLocationMap).toEqual(expectedPrevLocationMap);
}
- expect(testAlertActionArr).toMatchObject(expectedAlertResults);
+ expect(alerts).toMatchObject(expectedAlerts);
});
});
diff --git a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.ts b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.ts
index e7681eb4dee2d..b6462aec32497 100644
--- a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.ts
+++ b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/executor.ts
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { AlertsClientError } from '@kbn/alerting-plugin/server';
import { RuleExecutorOptions } from '../../types';
import {
canSkipBoundariesFetch,
@@ -45,6 +46,11 @@ export async function executor({
boundaryNameField: params.boundaryNameField,
boundaryIndexQuery: params.boundaryIndexQuery,
};
+
+ if (!services.alertsClient) {
+ throw new AlertsClientError();
+ }
+
const { shapesFilters, shapesIdsNamesMap } =
state.shapesFilters &&
canSkipBoundariesFetch(boundariesRequestMeta, state.boundariesRequestMeta)
@@ -82,14 +88,13 @@ export async function executor({
const { activeEntities, inactiveEntities } = getEntitiesAndGenerateAlerts(
prevLocationMap,
currLocationMap,
- services.alertFactory,
+ services.alertsClient,
shapesIdsNamesMap,
windowEnd
);
- const { getRecoveredAlerts } = services.alertFactory.done();
- for (const recoveredAlert of getRecoveredAlerts()) {
- const recoveredAlertId = recoveredAlert.getId();
+ for (const recoveredAlert of services.alertsClient.getRecoveredAlerts()) {
+ const recoveredAlertId = recoveredAlert.alert.getId();
try {
const context = getRecoveredAlertContext({
alertId: recoveredAlertId,
@@ -98,7 +103,10 @@ export async function executor({
windowEnd,
});
if (context) {
- recoveredAlert.setContext(context);
+ services.alertsClient?.setAlertData({
+ id: recoveredAlertId,
+ context,
+ });
}
} catch (e) {
logger.warn(`Unable to set alert context for recovered alert, error: ${e.message}`);
diff --git a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.test.ts b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.test.ts
index 339e53b03056b..c8e8dd8faa4d5 100644
--- a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.test.ts
+++ b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.test.ts
@@ -5,47 +5,28 @@
* 2.0.
*/
-import _ from 'lodash';
-import { alertsMock } from '@kbn/alerting-plugin/server/mocks';
import { getEntitiesAndGenerateAlerts } from './get_entities_and_generate_alerts';
import { OTHER_CATEGORY } from '../constants';
-import type {
- GeoContainmentAlertInstanceState,
- GeoContainmentAlertInstanceContext,
-} from '../types';
-
-const alertFactory = (contextKeys: unknown[], testAlertActionArr: unknown[]) => ({
- create: (instanceId: string) => {
- const alertInstance = alertsMock.createAlertFactory.create<
- GeoContainmentAlertInstanceState,
- GeoContainmentAlertInstanceContext
- >();
- (alertInstance.scheduleActions as jest.Mock).mockImplementation(
- (actionGroupId: string, context?: GeoContainmentAlertInstanceContext) => {
- // Check subset of alert for comparison to expected results
- // @ts-ignore
- const contextSubset = _.pickBy(context, (v, k) => contextKeys.includes(k));
- testAlertActionArr.push({
- actionGroupId,
- instanceId,
- context: contextSubset,
- });
- }
- );
- return alertInstance;
- },
- alertLimit: {
- getValue: () => 1000,
- setLimitReached: () => {},
- },
- done: () => ({ getRecoveredAlerts: () => [] }),
-});
+import type { GeoContainmentAlertInstanceContext } from '../types';
describe('getEntitiesAndGenerateAlerts', () => {
- const testAlertActionArr: unknown[] = [];
+ const alerts: unknown[] = [];
+ const mockAlertsClient = {
+ report: ({ id, context }: { id: string; context: GeoContainmentAlertInstanceContext }) => {
+ alerts.push({
+ context: {
+ containingBoundaryId: context.containingBoundaryId,
+ entityDocumentId: context.entityDocumentId,
+ entityId: context.entityId,
+ entityLocation: context.entityLocation,
+ },
+ instanceId: id,
+ });
+ },
+ } as any; // eslint-disable-line @typescript-eslint/no-explicit-any
+
beforeEach(() => {
- jest.clearAllMocks();
- testAlertActionArr.length = 0;
+ alerts.length = 0;
});
const currLocationMap = new Map([
@@ -87,9 +68,8 @@ describe('getEntitiesAndGenerateAlerts', () => {
],
]);
- const expectedAlertResults = [
+ const expectedAlerts = [
{
- actionGroupId: 'Tracked entity contained',
context: {
containingBoundaryId: '123',
entityDocumentId: 'docId1',
@@ -99,7 +79,6 @@ describe('getEntitiesAndGenerateAlerts', () => {
instanceId: 'a-123',
},
{
- actionGroupId: 'Tracked entity contained',
context: {
containingBoundaryId: '456',
entityDocumentId: 'docId2',
@@ -109,7 +88,6 @@ describe('getEntitiesAndGenerateAlerts', () => {
instanceId: 'b-456',
},
{
- actionGroupId: 'Tracked entity contained',
context: {
containingBoundaryId: '789',
entityDocumentId: 'docId3',
@@ -119,7 +97,6 @@ describe('getEntitiesAndGenerateAlerts', () => {
instanceId: 'c-789',
},
];
- const contextKeys = Object.keys(expectedAlertResults[0].context);
const emptyShapesIdsNamesMap = {};
const currentDateTime = new Date();
@@ -129,12 +106,12 @@ describe('getEntitiesAndGenerateAlerts', () => {
const { activeEntities } = getEntitiesAndGenerateAlerts(
emptyPrevLocationMap,
currLocationMap,
- alertFactory(contextKeys, testAlertActionArr),
+ mockAlertsClient,
emptyShapesIdsNamesMap,
currentDateTime
);
expect(activeEntities).toEqual(currLocationMap);
- expect(testAlertActionArr).toMatchObject(expectedAlertResults);
+ expect(alerts).toMatchObject(expectedAlerts);
});
test('should overwrite older identical entity entries', () => {
@@ -155,12 +132,12 @@ describe('getEntitiesAndGenerateAlerts', () => {
const { activeEntities } = getEntitiesAndGenerateAlerts(
prevLocationMapWithIdenticalEntityEntry,
currLocationMap,
- alertFactory(contextKeys, testAlertActionArr),
+ mockAlertsClient,
emptyShapesIdsNamesMap,
currentDateTime
);
expect(activeEntities).toEqual(currLocationMap);
- expect(testAlertActionArr).toMatchObject(expectedAlertResults);
+ expect(alerts).toMatchObject(expectedAlerts);
});
test('should preserve older non-identical entity entries', () => {
@@ -178,30 +155,28 @@ describe('getEntitiesAndGenerateAlerts', () => {
],
],
]);
- const expectedAlertResultsPlusD = [
- {
- actionGroupId: 'Tracked entity contained',
- context: {
- containingBoundaryId: '999',
- entityDocumentId: 'docId7',
- entityId: 'd',
- entityLocation: 'POINT (0 0)',
- },
- instanceId: 'd-999',
- },
- ...expectedAlertResults,
- ];
const { activeEntities } = getEntitiesAndGenerateAlerts(
prevLocationMapWithNonIdenticalEntityEntry,
currLocationMap,
- alertFactory(contextKeys, testAlertActionArr),
+ mockAlertsClient,
emptyShapesIdsNamesMap,
currentDateTime
);
expect(activeEntities).not.toEqual(currLocationMap);
expect(activeEntities.has('d')).toBeTruthy();
- expect(testAlertActionArr).toMatchObject(expectedAlertResultsPlusD);
+ expect(alerts).toMatchObject([
+ {
+ context: {
+ containingBoundaryId: '999',
+ entityDocumentId: 'docId7',
+ entityId: 'd',
+ entityLocation: 'POINT (0 0)',
+ },
+ instanceId: 'd-999',
+ },
+ ...expectedAlerts,
+ ]);
});
test('should remove "other" entries and schedule the expected number of actions', () => {
@@ -219,7 +194,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
const { activeEntities, inactiveEntities } = getEntitiesAndGenerateAlerts(
emptyPrevLocationMap,
currLocationMapWithOther,
- alertFactory(contextKeys, testAlertActionArr),
+ mockAlertsClient,
emptyShapesIdsNamesMap,
currentDateTime
);
@@ -240,7 +215,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
],
])
);
- expect(testAlertActionArr).toMatchObject(expectedAlertResults);
+ expect(alerts).toMatchObject(expectedAlerts);
});
test('should generate multiple alerts per entity if found in multiple shapes in interval', () => {
@@ -271,7 +246,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
getEntitiesAndGenerateAlerts(
emptyPrevLocationMap,
currLocationMapWithThreeMore,
- alertFactory(contextKeys, testAlertActionArr),
+ mockAlertsClient,
emptyShapesIdsNamesMap,
currentDateTime
);
@@ -279,7 +254,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
currLocationMapWithThreeMore.forEach((v) => {
numEntitiesInShapes += v.length;
});
- expect(testAlertActionArr.length).toEqual(numEntitiesInShapes);
+ expect(alerts.length).toEqual(numEntitiesInShapes);
});
test('should not return entity as active entry if most recent location is "other"', () => {
@@ -311,7 +286,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
const { activeEntities } = getEntitiesAndGenerateAlerts(
emptyPrevLocationMap,
currLocationMapWithOther,
- alertFactory(contextKeys, testAlertActionArr),
+ mockAlertsClient,
emptyShapesIdsNamesMap,
currentDateTime
);
@@ -346,7 +321,7 @@ describe('getEntitiesAndGenerateAlerts', () => {
const { activeEntities } = getEntitiesAndGenerateAlerts(
emptyPrevLocationMap,
currLocationMapWithOther,
- alertFactory(contextKeys, testAlertActionArr),
+ mockAlertsClient,
emptyShapesIdsNamesMap,
currentDateTime
);
diff --git a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.ts b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.ts
index c0d372e08dced..5ec7fcedb5eed 100644
--- a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.ts
+++ b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/lib/get_entities_and_generate_alerts.ts
@@ -17,11 +17,11 @@ import { getAlertId, getContainedAlertContext } from './alert_context';
export function getEntitiesAndGenerateAlerts(
prevLocationMap: Map,
currLocationMap: Map,
- alertFactory: RuleExecutorServices<
+ alertsClient: RuleExecutorServices<
GeoContainmentAlertInstanceState,
GeoContainmentAlertInstanceContext,
typeof ActionGroupId
- >['alertFactory'],
+ >['alertsClient'],
shapesIdsNamesMap: Record,
windowEnd: Date
): {
@@ -43,9 +43,11 @@ export function getEntitiesAndGenerateAlerts(
shapesIdsNamesMap,
windowEnd,
});
- alertFactory
- .create(getAlertId(entityName, context.containingBoundaryName))
- .scheduleActions(ActionGroupId, context);
+ alertsClient!.report({
+ id: getAlertId(entityName, context.containingBoundaryName),
+ actionGroup: ActionGroupId,
+ context,
+ });
}
});
diff --git a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts
index 832e6499dc02b..f7ea3b3601453 100644
--- a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts
+++ b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts
@@ -17,6 +17,7 @@ import type {
} from './types';
import { executor } from './executor';
import { ActionGroupId, RecoveryActionGroupId, GEO_CONTAINMENT_ID } from './constants';
+import { STACK_ALERTS_AAD_CONFIG } from '../constants';
const actionVariables = {
context: [
@@ -200,5 +201,7 @@ export function getRuleType(): GeoContainmentRuleType {
return injectEntityAndBoundaryIds(params, references);
},
},
+ // @ts-ignore
+ alerts: STACK_ALERTS_AAD_CONFIG,
};
}
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index a9ec11b624d36..900ecb03cacc0 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -18390,7 +18390,6 @@
"xpack.idxMgmt.createIndex.successfullyCreatedIndexMessage": "Création réussie de l'index : {indexName}",
"xpack.idxMgmt.dataStreamList.dataStreamsDescription": "Les flux de données conservent des données de séries temporelles sur plusieurs index. {learnMoreLink}",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIndexTemplateMessage": "Lancez-vous avec les flux de données en créant un {link}.",
- "xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerMessage": "Lancez-vous avec les flux de données dans {link}.",
"xpack.idxMgmt.dataStreamList.table.deleteDataStreamsButtonLabel": "Supprimer {count, plural, one {le flux de données} many {flux de données} other {flux de données}}",
"xpack.idxMgmt.deleteDataStreamsConfirmationModal.confirmButtonLabel": "Supprimer {dataStreamsCount, plural, one {le flux de données} many {flux de données} other {flux de données}}",
"xpack.idxMgmt.deleteDataStreamsConfirmationModal.deleteDescription": "Vous êtes sur le point de supprimer {dataStreamsCount, plural, one {ce flux de données} many {ces flux de données} other {ces flux de données}} :",
@@ -18687,7 +18686,6 @@
"xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle": "Champ d'horodatage",
"xpack.idxMgmt.dataStreamDetailPanel.timestampFieldToolTip": "Champ d'horodatage partagé par tous les documents du flux de données.",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIndexTemplateLink": "modèle d'index composable",
- "xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerLink": "Fleet",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsDescription": "Les flux de données conservent des données de séries temporelles sur plusieurs index.",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsTitle": "Vous n'avez pas encore de flux de données",
"xpack.idxMgmt.dataStreamList.loadingDataStreamsDescription": "Chargement des flux de données en cours…",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 13807b5f6c171..a506cb6a837e9 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -18403,7 +18403,6 @@
"xpack.idxMgmt.createIndex.successfullyCreatedIndexMessage": "インデックスの作成が正常に完了しました:{indexName}",
"xpack.idxMgmt.dataStreamList.dataStreamsDescription": "データストリームは複数のインデックスの時系列データを格納します。{learnMoreLink}",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIndexTemplateMessage": "{link}を作成して、データストリームを開始します。",
- "xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerMessage": "{link}でデータストリームを開始します。",
"xpack.idxMgmt.dataStreamList.table.deleteDataStreamsButtonLabel": "{count, plural, other {データストリーム}}削除",
"xpack.idxMgmt.deleteDataStreamsConfirmationModal.confirmButtonLabel": "{dataStreamsCount, plural, other {データストリーム}}削除",
"xpack.idxMgmt.deleteDataStreamsConfirmationModal.deleteDescription": "{dataStreamsCount, plural, other {これらのデータストリーム}}を削除しようとしています:",
@@ -18700,7 +18699,6 @@
"xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle": "タイムスタンプフィールド",
"xpack.idxMgmt.dataStreamDetailPanel.timestampFieldToolTip": "タイムスタンプフィールドはデータストリームのすべてのドキュメントで共有されます。",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIndexTemplateLink": "作成可能なインデックステンプレート",
- "xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerLink": "Fleet",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsDescription": "データストリームは複数のインデックスの時系列データを格納します。",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsTitle": "まだデータストリームがありません",
"xpack.idxMgmt.dataStreamList.loadingDataStreamsDescription": "データストリームを読み込んでいます…",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 84528f40edf26..0074dfd450b80 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -18470,7 +18470,6 @@
"xpack.idxMgmt.createIndex.successfullyCreatedIndexMessage": "已成功创建索引:{indexName}",
"xpack.idxMgmt.dataStreamList.dataStreamsDescription": "数据流在多个索引上存储时序数据。{learnMoreLink}",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIndexTemplateMessage": "通过创建 {link} 来开始使用数据流。",
- "xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerMessage": "开始使用 {link} 中的数据流。",
"xpack.idxMgmt.dataStreamList.table.deleteDataStreamsButtonLabel": "删除 {count, plural, other {数据流}}",
"xpack.idxMgmt.deleteDataStreamsConfirmationModal.confirmButtonLabel": "删除 {dataStreamsCount, plural, other {数据流}}",
"xpack.idxMgmt.deleteDataStreamsConfirmationModal.deleteDescription": "您即将删除{dataStreamsCount, plural, other {以下数据流}}:",
@@ -18767,7 +18766,6 @@
"xpack.idxMgmt.dataStreamDetailPanel.timestampFieldTitle": "时间戳字段",
"xpack.idxMgmt.dataStreamDetailPanel.timestampFieldToolTip": "时间戳字段由数据流中的所有文档共享。",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIndexTemplateLink": "可组合索引模板",
- "xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerLink": "Fleet",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsDescription": "数据流存储多个索引的时序数据。",
"xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsTitle": "您尚未有任何数据流",
"xpack.idxMgmt.dataStreamList.loadingDataStreamsDescription": "正在加载数据流……",
diff --git a/x-pack/test/accessibility/apps/group1/dashboard_panel_options.ts b/x-pack/test/accessibility/apps/group1/dashboard_panel_options.ts
index 5f12f3600c29a..a5182a8d2ca03 100644
--- a/x-pack/test/accessibility/apps/group1/dashboard_panel_options.ts
+++ b/x-pack/test/accessibility/apps/group1/dashboard_panel_options.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
-import type { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const a11y = getService('a11y');
diff --git a/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts
index 022ab5e3b32ff..2c7d0ce250b02 100644
--- a/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts
+++ b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts
@@ -93,7 +93,8 @@ export default function ({ getService }: FtrProviderContext) {
log.debug('CSP plugin is initialized');
});
- describe('Verify update csp rules states API', async () => {
+ // Failing: See https://github.com/elastic/kibana/issues/174204
+ describe.skip('Verify update csp rules states API', async () => {
before(async () => {
await waitForPluginInitialized();
});
diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts
index e68d1ebc60953..b0dbc3645ee8f 100644
--- a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts
+++ b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts
@@ -15,7 +15,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const dashboardPanelActions = getService('dashboardPanelActions');
const dashboardBadgeActions = getService('dashboardBadgeActions');
const dashboardCustomizePanel = getService('dashboardCustomizePanel');
- const dashboardAddPanel = getService('dashboardAddPanel');
const PageObjects = getPageObjects([
'common',
'dashboard',
@@ -87,19 +86,5 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await testSubjects.exists('xyVisChart')).to.be(true);
});
});
-
- describe('embeddable that does not support time', () => {
- it('should not show custom time picker in flyout', async () => {
- await dashboardPanelActions.removePanel();
- await PageObjects.dashboard.waitForRenderComplete();
- await dashboardAddPanel.clickMarkdownQuickButton();
- await PageObjects.visEditor.setMarkdownTxt('I am timeless!');
- await PageObjects.visEditor.clickGo();
- await PageObjects.visualize.saveVisualizationAndReturn();
- await PageObjects.dashboard.clickQuickSave();
- await dashboardPanelActions.customizePanel();
- await dashboardCustomizePanel.expectMissingCustomTimeRange();
- });
- });
});
}
diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts b/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts
index 2c0ac33107fea..4d7950042783a 100644
--- a/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts
+++ b/x-pack/test/functional/apps/dashboard/group2/panel_titles.ts
@@ -78,44 +78,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(panelTitle).to.equal(EMPTY_TITLE);
await PageObjects.dashboard.clearUnsavedChanges();
});
-
- it('blank titles are hidden in view mode', async () => {
- await PageObjects.dashboard.clickCancelOutOfEditMode();
-
- const titleVisibility = (await PageObjects.dashboard.getVisibilityOfPanelTitles())[0];
- expect(titleVisibility).to.be(false);
- });
-
- it('custom titles are visible in view mode', async () => {
- await PageObjects.dashboard.switchToEditMode();
- await dashboardPanelActions.customizePanel();
- await dashboardCustomizePanel.setCustomPanelTitle(CUSTOM_TITLE);
- await dashboardCustomizePanel.clickSaveButton();
- await PageObjects.dashboard.clickQuickSave();
- await PageObjects.dashboard.clickCancelOutOfEditMode();
-
- const titleVisibility = (await PageObjects.dashboard.getVisibilityOfPanelTitles())[0];
- expect(titleVisibility).to.be(true);
- });
-
- it('hiding an individual panel title hides it in view mode', async () => {
- await PageObjects.dashboard.switchToEditMode();
- await dashboardPanelActions.customizePanel();
- await dashboardCustomizePanel.clickToggleHidePanelTitle();
- await dashboardCustomizePanel.clickSaveButton();
- await PageObjects.dashboard.clickQuickSave();
- await PageObjects.dashboard.clickCancelOutOfEditMode();
-
- const titleVisibility = (await PageObjects.dashboard.getVisibilityOfPanelTitles())[0];
- expect(titleVisibility).to.be(false);
-
- // undo the previous hide panel toggle (i.e. make the panel visible) to keep state consistent
- await PageObjects.dashboard.switchToEditMode();
- await dashboardPanelActions.customizePanel();
- await dashboardCustomizePanel.clickToggleHidePanelTitle();
- await dashboardCustomizePanel.clickSaveButton();
- await PageObjects.dashboard.clickQuickSave();
- });
});
describe('by reference', () => {
diff --git a/x-pack/test/functional/apps/discover/saved_queries.ts b/x-pack/test/functional/apps/discover/saved_queries.ts
index 466524f4dbf80..3daf70e3f560c 100644
--- a/x-pack/test/functional/apps/discover/saved_queries.ts
+++ b/x-pack/test/functional/apps/discover/saved_queries.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
@@ -13,7 +12,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
const spaces = getService('spaces');
- const toasts = getService('toasts');
const PageObjects = getPageObjects([
'common',
'discover',
@@ -26,8 +24,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const savedQueryName = 'shared-saved-query';
const destinationSpaceId = 'nondefaultspace';
- // Failing: See https://github.com/elastic/kibana/issues/173094
- describe.skip('Discover Saved Queries', () => {
+ describe('Discover Saved Queries', () => {
before('initialize tests', async () => {
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional');
await kibanaServer.importExport.load(
@@ -53,6 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
// Navigate to Discover & create a saved query
await PageObjects.common.navigateToApp('discover');
await queryBar.setQuery('response:200');
+ await queryBar.submitQuery();
await savedQueryManagementComponent.saveNewQuery(savedQueryName, '', true, false);
await savedQueryManagementComponent.savedQueryExistOrFail(savedQueryName);
await savedQueryManagementComponent.closeSavedQueryManagementComponent();
@@ -76,24 +74,26 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
});
it('updates a saved query', async () => {
+ const name = `${savedQueryName}-update`;
+
// Navigate to Discover & create a saved query
await PageObjects.common.navigateToApp('discover');
await queryBar.setQuery('response:200');
- await savedQueryManagementComponent.saveNewQuery(savedQueryName, '', true, false);
- await savedQueryManagementComponent.savedQueryExistOrFail(savedQueryName);
+ await queryBar.submitQuery();
+ await savedQueryManagementComponent.saveNewQuery(name, '', true, false);
+ await savedQueryManagementComponent.savedQueryExistOrFail(name);
await savedQueryManagementComponent.closeSavedQueryManagementComponent();
- // Navigate to Discover & create a saved query
+ // Update the saved query
await queryBar.setQuery('response:404');
+ await queryBar.submitQuery();
await savedQueryManagementComponent.updateCurrentlyLoadedQuery('', true, false);
- // Expect to see a success toast
- const successToast = await toasts.getToastElement(1);
- const successText = await successToast.getVisibleText();
- expect(successText).to.equal(`Your query "${savedQueryName}" was saved`);
-
+ // Navigate to Discover ensure updated query exists
await PageObjects.common.navigateToApp('discover');
- await savedQueryManagementComponent.deleteSavedQuery(savedQueryName);
+ await savedQueryManagementComponent.savedQueryExistOrFail(name);
+ await savedQueryManagementComponent.closeSavedQueryManagementComponent();
+ await savedQueryManagementComponent.deleteSavedQuery(name);
});
});
});
diff --git a/x-pack/test/functional/apps/discover/visualize_field.ts b/x-pack/test/functional/apps/discover/visualize_field.ts
index 428ef13c34866..bbd5e94dc4c75 100644
--- a/x-pack/test/functional/apps/discover/visualize_field.ts
+++ b/x-pack/test/functional/apps/discover/visualize_field.ts
@@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { DebugState } from '@elastic/charts';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
diff --git a/x-pack/test/functional/apps/index_management/data_streams_tab/data_streams_tab.ts b/x-pack/test/functional/apps/index_management/data_streams_tab/data_streams_tab.ts
index 9d3da94fead4a..4480063da220b 100644
--- a/x-pack/test/functional/apps/index_management/data_streams_tab/data_streams_tab.ts
+++ b/x-pack/test/functional/apps/index_management/data_streams_tab/data_streams_tab.ts
@@ -78,7 +78,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('shows the details flyout when clicking on a data stream', async () => {
// Open details flyout
- await pageObjects.indexManagement.clickDataStreamAt(0);
+ await pageObjects.indexManagement.clickDataStreamNameLink(TEST_DS_NAME);
// Verify url is stateful
const url = await browser.getCurrentUrl();
expect(url).to.contain(`/data_streams/${TEST_DS_NAME}`);
@@ -90,7 +90,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('allows to update data retention', async () => {
// Open details flyout
- await pageObjects.indexManagement.clickDataStreamAt(0);
+ await pageObjects.indexManagement.clickDataStreamNameLink(TEST_DS_NAME);
// Open the edit retention dialog
await testSubjects.click('manageDataStreamButton');
await testSubjects.click('editDataRetentionButton');
@@ -112,7 +112,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('allows to disable data retention', async () => {
// Open details flyout
- await pageObjects.indexManagement.clickDataStreamAt(0);
+ await pageObjects.indexManagement.clickDataStreamNameLink(TEST_DS_NAME);
// Open the edit retention dialog
await testSubjects.click('manageDataStreamButton');
await testSubjects.click('editDataRetentionButton');
diff --git a/x-pack/test/functional/apps/infra/hosts_view.ts b/x-pack/test/functional/apps/infra/hosts_view.ts
index f9932421690e0..13657713faac7 100644
--- a/x-pack/test/functional/apps/infra/hosts_view.ts
+++ b/x-pack/test/functional/apps/infra/hosts_view.ts
@@ -10,7 +10,7 @@ import expect from '@kbn/expect';
import { parse } from 'url';
import { enableInfrastructureHostsView } from '@kbn/observability-plugin/common';
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
import {
DATES,
diff --git a/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts b/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts
index d656301a13891..98cfc79c561ad 100644
--- a/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts
+++ b/x-pack/test/functional/apps/observability_log_explorer/dataset_selector.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
import expect from '@kbn/expect';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from './config';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
const initialPackageMap = {
apache: 'Apache HTTP Server',
diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts
index 503e8666e0bef..810048a7f144f 100644
--- a/x-pack/test/functional/page_objects/graph_page.ts
+++ b/x-pack/test/functional/page_objects/graph_page.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrService } from '../ftr_provider_context';
interface Node {
diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts
index 9dc9d30fd375d..19eb3f2824f3a 100644
--- a/x-pack/test/functional/page_objects/index_management_page.ts
+++ b/x-pack/test/functional/page_objects/index_management_page.ts
@@ -22,9 +22,6 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext)
async reloadIndicesButton() {
return await testSubjects.find('reloadIndicesButton');
},
- async toggleRollupIndices() {
- await testSubjects.click('checkboxToggles-rollupToggle');
- },
async toggleHiddenIndices() {
await testSubjects.click('indexTableIncludeHiddenIndicesToggle');
},
@@ -34,9 +31,8 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext)
await policyDetailsLinks[indexOfRow].click();
},
- async clickDataStreamAt(indexOfRow: number): Promise {
- const dataStreamLinks = await testSubjects.findAll('nameLink');
- await dataStreamLinks[indexOfRow].click();
+ async clickDataStreamNameLink(name: string): Promise {
+ await find.clickByLinkText(name);
},
async clickDeleteEnrichPolicyAt(indexOfRow: number): Promise {
diff --git a/x-pack/test/functional/page_objects/infra_hosts_view.ts b/x-pack/test/functional/page_objects/infra_hosts_view.ts
index 3fbb3d361d879..0c3004acc4fe8 100644
--- a/x-pack/test/functional/page_objects/infra_hosts_view.ts
+++ b/x-pack/test/functional/page_objects/infra_hosts_view.ts
@@ -6,7 +6,7 @@
*/
import { AlertStatus } from '@kbn/rule-data-utils';
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
export function InfraHostsViewProvider({ getService }: FtrProviderContext) {
diff --git a/x-pack/test/functional/page_objects/infra_metrics_explorer.ts b/x-pack/test/functional/page_objects/infra_metrics_explorer.ts
index e8d9f878b43c5..4e691a164cdb7 100644
--- a/x-pack/test/functional/page_objects/infra_metrics_explorer.ts
+++ b/x-pack/test/functional/page_objects/infra_metrics_explorer.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
export function InfraMetricsExplorerProvider({ getService }: FtrProviderContext) {
diff --git a/x-pack/test/functional/page_objects/ingest_pipelines_page.ts b/x-pack/test/functional/page_objects/ingest_pipelines_page.ts
index 3e0bc23869bb7..c4800b55aa77c 100644
--- a/x-pack/test/functional/page_objects/ingest_pipelines_page.ts
+++ b/x-pack/test/functional/page_objects/ingest_pipelines_page.ts
@@ -6,7 +6,7 @@
*/
import path from 'path';
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
export function IngestPipelinesPageProvider({ getService, getPageObjects }: FtrProviderContext) {
diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts
index ee9ad1596fbe0..2d3766c137667 100644
--- a/x-pack/test/functional/page_objects/lens_page.ts
+++ b/x-pack/test/functional/page_objects/lens_page.ts
@@ -9,7 +9,7 @@ import expect from '@kbn/expect';
import { setTimeout as setTimeoutAsync } from 'timers/promises';
import type { FittingFunction, XYCurveType } from '@kbn/lens-plugin/public';
import { DebugState } from '@elastic/charts';
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
import { logWrapper } from './log_wrapper';
diff --git a/x-pack/test/functional/page_objects/navigational_search.ts b/x-pack/test/functional/page_objects/navigational_search.ts
index ae27d6d68a4a5..54c1582f322cc 100644
--- a/x-pack/test/functional/page_objects/navigational_search.ts
+++ b/x-pack/test/functional/page_objects/navigational_search.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrService } from '../ftr_provider_context';
interface SearchResult {
diff --git a/x-pack/test/functional/page_objects/observability_log_explorer.ts b/x-pack/test/functional/page_objects/observability_log_explorer.ts
index a61682e62a8cb..07cdf818a7ae0 100644
--- a/x-pack/test/functional/page_objects/observability_log_explorer.ts
+++ b/x-pack/test/functional/page_objects/observability_log_explorer.ts
@@ -11,7 +11,7 @@ import {
} from '@kbn/observability-log-explorer-plugin/common';
import rison from '@kbn/rison';
import querystring from 'querystring';
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
export interface IntegrationPackage {
diff --git a/x-pack/test/functional/page_objects/tag_management_page.ts b/x-pack/test/functional/page_objects/tag_management_page.ts
index 5eb93aa977cf5..30c6689d87b8f 100644
--- a/x-pack/test/functional/page_objects/tag_management_page.ts
+++ b/x-pack/test/functional/page_objects/tag_management_page.ts
@@ -7,7 +7,7 @@
/* eslint-disable max-classes-per-file */
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrService, FtrProviderContext } from '../ftr_provider_context';
interface FillTagFormFields {
diff --git a/x-pack/test/functional/services/cases/list.ts b/x-pack/test/functional/services/cases/list.ts
index 61d588dc261ed..03d1078ccec2c 100644
--- a/x-pack/test/functional/services/cases/list.ts
+++ b/x-pack/test/functional/services/cases/list.ts
@@ -6,7 +6,7 @@
*/
import { CaseSeverity, CaseStatuses } from '@kbn/cases-plugin/common/types/domain';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
import { CasesCommon } from './common';
diff --git a/x-pack/test/functional/services/infra_source_configuration_form.ts b/x-pack/test/functional/services/infra_source_configuration_form.ts
index 741d42ac16fda..805dfcbbc9dcb 100644
--- a/x-pack/test/functional/services/infra_source_configuration_form.ts
+++ b/x-pack/test/functional/services/infra_source_configuration_form.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
export function InfraSourceConfigurationFormProvider({
getService,
diff --git a/x-pack/test/functional/services/logs_ui/log_entry_categories.ts b/x-pack/test/functional/services/logs_ui/log_entry_categories.ts
index 0aec1cbea2210..77098bd918ea6 100644
--- a/x-pack/test/functional/services/logs_ui/log_entry_categories.ts
+++ b/x-pack/test/functional/services/logs_ui/log_entry_categories.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
export function LogEntryCategoriesPageProvider({ getPageObjects, getService }: FtrProviderContext) {
diff --git a/x-pack/test/functional/services/logs_ui/log_entry_rate.ts b/x-pack/test/functional/services/logs_ui/log_entry_rate.ts
index bf58d74a06c44..f8a68f6c924e0 100644
--- a/x-pack/test/functional/services/logs_ui/log_entry_rate.ts
+++ b/x-pack/test/functional/services/logs_ui/log_entry_rate.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
export function LogEntryRatePageProvider({ getPageObjects, getService }: FtrProviderContext) {
diff --git a/x-pack/test/functional/services/logs_ui/log_stream.ts b/x-pack/test/functional/services/logs_ui/log_stream.ts
index 1a068439a2d2d..160e949c84de4 100644
--- a/x-pack/test/functional/services/logs_ui/log_stream.ts
+++ b/x-pack/test/functional/services/logs_ui/log_stream.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
import { TabsParams } from '../../page_objects/infra_logs_page';
export function LogStreamPageProvider({ getPageObjects, getService }: FtrProviderContext) {
diff --git a/x-pack/test/functional/services/ml/common_table_service.ts b/x-pack/test/functional/services/ml/common_table_service.ts
index ac403d62b5ac8..683ac91f4e7a3 100644
--- a/x-pack/test/functional/services/ml/common_table_service.ts
+++ b/x-pack/test/functional/services/ml/common_table_service.ts
@@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
export type MlTableService = ReturnType;
diff --git a/x-pack/test/functional/services/ml/common_ui.ts b/x-pack/test/functional/services/ml/common_ui.ts
index dc4836ed6f34c..63a1fdf17cf4f 100644
--- a/x-pack/test/functional/services/ml/common_ui.ts
+++ b/x-pack/test/functional/services/ml/common_ui.ts
@@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { ProvidedType } from '@kbn/test';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
import type { CanvasElementColorStats } from '../canvas_element';
diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_results.ts b/x-pack/test/functional/services/ml/data_frame_analytics_results.ts
index 0fc99e1e032a1..17a409a082ce7 100644
--- a/x-pack/test/functional/services/ml/data_frame_analytics_results.ts
+++ b/x-pack/test/functional/services/ml/data_frame_analytics_results.ts
@@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
import type { CanvasElementColorStats } from '../canvas_element';
import type { MlCommonUI } from './common_ui';
diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_table.ts b/x-pack/test/functional/services/ml/data_frame_analytics_table.ts
index 2b89364644a36..c85e38cd78b8e 100644
--- a/x-pack/test/functional/services/ml/data_frame_analytics_table.ts
+++ b/x-pack/test/functional/services/ml/data_frame_analytics_table.ts
@@ -8,7 +8,7 @@
import expect from '@kbn/expect';
import { ProvidedType } from '@kbn/test';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
type ExpectedSectionTableEntries = Record;
diff --git a/x-pack/test/functional/services/ml/stack_management_jobs.ts b/x-pack/test/functional/services/ml/stack_management_jobs.ts
index f8db28deb5092..52b8e005ac94c 100644
--- a/x-pack/test/functional/services/ml/stack_management_jobs.ts
+++ b/x-pack/test/functional/services/ml/stack_management_jobs.ts
@@ -14,7 +14,7 @@ import path from 'path';
import type { JobType, MlSavedObjectType } from '@kbn/ml-plugin/common/types/saved_objects';
import type { Job, Datafeed } from '@kbn/ml-plugin/common/types/anomaly_detection_jobs';
import type { DataFrameAnalyticsConfig } from '@kbn/ml-data-frame-analytics-utils';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import type { FtrProviderContext } from '../../ftr_provider_context';
type SyncFlyoutObjectType =
diff --git a/x-pack/test/functional/services/ml/swim_lane.ts b/x-pack/test/functional/services/ml/swim_lane.ts
index 54895918a6d0d..d9ad1dd44d98a 100644
--- a/x-pack/test/functional/services/ml/swim_lane.ts
+++ b/x-pack/test/functional/services/ml/swim_lane.ts
@@ -9,8 +9,8 @@ import expect from '@kbn/expect';
import { ProvidedType } from '@kbn/test';
import { DebugState } from '@elastic/charts';
import { DebugStateAxis } from '@elastic/charts/dist/state/types';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
type HeatmapDebugState = Required>;
diff --git a/x-pack/test/functional/services/ml/trained_models_table.ts b/x-pack/test/functional/services/ml/trained_models_table.ts
index a186c531703be..4ea56286de4fd 100644
--- a/x-pack/test/functional/services/ml/trained_models_table.ts
+++ b/x-pack/test/functional/services/ml/trained_models_table.ts
@@ -9,7 +9,7 @@ import expect from '@kbn/expect';
import { ProvidedType } from '@kbn/test';
import { upperFirst } from 'lodash';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import type { FtrProviderContext } from '../../ftr_provider_context';
import type { MlCommonUI } from './common_ui';
import { MappedInputParams, MappedOutput, ModelType, TrainedModelsActions } from './trained_models';
diff --git a/x-pack/test/functional/services/observability/alerts/common.ts b/x-pack/test/functional/services/observability/alerts/common.ts
index 57403ef8c3ab3..617fadcf25e28 100644
--- a/x-pack/test/functional/services/observability/alerts/common.ts
+++ b/x-pack/test/functional/services/observability/alerts/common.ts
@@ -8,8 +8,8 @@
import expect from '@kbn/expect';
import { chunk } from 'lodash';
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, AlertStatus } from '@kbn/rule-data-utils';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../../ftr_provider_context';
-import { WebElementWrapper } from '../../../../../../test/functional/services/lib/web_element_wrapper';
// Based on the x-pack/test/functional/es_archives/observability/alerts archive.
const DATE_WITH_DATA = {
diff --git a/x-pack/test/functional/services/search_sessions.ts b/x-pack/test/functional/services/search_sessions.ts
index a22e635742686..cd0b60d321279 100644
--- a/x-pack/test/functional/services/search_sessions.ts
+++ b/x-pack/test/functional/services/search_sessions.ts
@@ -9,7 +9,7 @@ import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { INITIAL_SEARCH_SESSION_REST_VERSION } from '@kbn/data-plugin/server';
import expect from '@kbn/expect';
import { SavedObjectsFindResponse } from '@kbn/core/server';
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrService } from '../ftr_provider_context';
const SEARCH_SESSION_INDICATOR_TEST_SUBJ = 'searchSessionIndicator';
diff --git a/x-pack/test/functional/services/transform/transform_table.ts b/x-pack/test/functional/services/transform/transform_table.ts
index 665b04b058106..7a5f303ba155b 100644
--- a/x-pack/test/functional/services/transform/transform_table.ts
+++ b/x-pack/test/functional/services/transform/transform_table.ts
@@ -7,7 +7,7 @@
import expect from '@kbn/expect';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
diff --git a/x-pack/test/functional_enterprise_search/page_objects/app_search.ts b/x-pack/test/functional_enterprise_search/page_objects/app_search.ts
index 8c02cdb705272..75fe3da03ed1e 100644
--- a/x-pack/test/functional_enterprise_search/page_objects/app_search.ts
+++ b/x-pack/test/functional_enterprise_search/page_objects/app_search.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
import { TestSubjects } from '../../../../test/functional/services/common';
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
export function AppSearchPageProvider({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['common']);
diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts
index f72677bff7f05..cdffa758420c0 100644
--- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts
+++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts
@@ -6,10 +6,7 @@
*/
import expect from '@kbn/expect';
-import {
- CustomCheerio,
- CustomCheerioStatic,
-} from '../../../../test/functional/services/lib/web_element_wrapper/custom_cheerio_api';
+import type { CustomCheerio, CustomCheerioStatic } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
const ENTER_KEY = '\uE007';
diff --git a/x-pack/test/plugin_functional/test_suites/resolver/index.ts b/x-pack/test/plugin_functional/test_suites/resolver/index.ts
index edc5d25971077..51fa158672623 100644
--- a/x-pack/test/plugin_functional/test_suites/resolver/index.ts
+++ b/x-pack/test/plugin_functional/test_suites/resolver/index.ts
@@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { panAnimationDuration } from '@kbn/security-solution-plugin/public/resolver/store/camera/scaling_constants';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts
index 85e2e602a8929..81923173bf50c 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/index.ts
@@ -15,5 +15,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./open_close_alerts'));
loadTestFile(require.resolve('./set_alert_tags'));
loadTestFile(require.resolve('./assignments'));
+ loadTestFile(require.resolve('./query_alerts'));
});
}
diff --git a/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/query_alerts.ts b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/query_alerts.ts
new file mode 100644
index 0000000000000..6e8e312f9075a
--- /dev/null
+++ b/x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/alerts/query_alerts.ts
@@ -0,0 +1,153 @@
+/*
+ * 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 expect from '@kbn/expect';
+
+import { DETECTION_ENGINE_QUERY_SIGNALS_URL } from '@kbn/security-solution-plugin/common/constants';
+import { FtrProviderContext } from '../../../../ftr_provider_context';
+
+const roleToAccessSecuritySolution = {
+ name: 'sec_all_spaces',
+ privileges: {
+ elasticsearch: {
+ indices: [
+ {
+ names: ['.alerts-security.alerts-default'],
+ privileges: ['all'],
+ },
+ ],
+ },
+ kibana: [
+ {
+ feature: {
+ siem: ['all'],
+ },
+ spaces: ['*'],
+ },
+ ],
+ },
+};
+const roleToAccessSecuritySolutionWithDls = {
+ name: 'sec_all_spaces_with_dls',
+ privileges: {
+ elasticsearch: {
+ indices: [
+ {
+ names: ['.alerts-security.alerts-default'],
+ privileges: ['read'],
+ query:
+ '{"wildcard" : { "kibana.alert.ancestors.index" : { "value": ".ds-kibana_does_not_exist" } } }',
+ },
+ ],
+ },
+ kibana: [
+ {
+ feature: {
+ siem: ['all'],
+ },
+ spaces: ['*'],
+ },
+ ],
+ },
+};
+const userAllSec = {
+ username: 'user_all_sec',
+ password: 'user_all_sec',
+ full_name: 'userAllSec',
+ email: 'userAllSec@elastic.co',
+ roles: [roleToAccessSecuritySolution.name],
+};
+const userAllSecWithDls = {
+ username: 'user_all_sec_with_dls',
+ password: 'user_all_sec_with_dls',
+ full_name: 'userAllSecWithDls',
+ email: 'userAllSecWithDls@elastic.co',
+ roles: [roleToAccessSecuritySolutionWithDls.name],
+};
+
+export default ({ getService }: FtrProviderContext) => {
+ const supertestWithoutAuth = getService('supertestWithoutAuth');
+ const esArchiver = getService('esArchiver');
+ const security = getService('security');
+
+ describe('find alert with/without doc level security', () => {
+ before(async () => {
+ await security.role.create(
+ roleToAccessSecuritySolution.name,
+ roleToAccessSecuritySolution.privileges
+ );
+ await security.role.create(
+ roleToAccessSecuritySolutionWithDls.name,
+ roleToAccessSecuritySolutionWithDls.privileges
+ );
+ await security.user.create(userAllSec.username, {
+ password: userAllSec.password,
+ roles: userAllSec.roles,
+ full_name: userAllSec.full_name,
+ email: userAllSec.email,
+ });
+ await security.user.create(userAllSecWithDls.username, {
+ password: userAllSecWithDls.password,
+ roles: userAllSecWithDls.roles,
+ full_name: userAllSecWithDls.full_name,
+ email: userAllSecWithDls.email,
+ });
+
+ await esArchiver.load(
+ 'x-pack/test/functional/es_archives/security_solution/alerts/8.8.0_multiple_docs',
+ {
+ useCreate: true,
+ docsOnly: true,
+ }
+ );
+ });
+
+ after(async () => {
+ await security.user.delete(userAllSec.username);
+ await security.user.delete(userAllSecWithDls.username);
+ await security.role.delete(roleToAccessSecuritySolution.name);
+ await security.role.delete(roleToAccessSecuritySolutionWithDls.name);
+ await esArchiver.unload(
+ 'x-pack/test/functional/es_archives/security_solution/alerts/8.8.0_multiple_docs'
+ );
+ });
+
+ it('should return alerts with user who has access to security solution privileges', async () => {
+ const query = {
+ query: {
+ bool: {
+ should: [{ match_all: {} }],
+ },
+ },
+ };
+ const { body } = await supertestWithoutAuth
+ .post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
+ .auth(userAllSec.username, userAllSec.password)
+ .set('kbn-xsrf', 'true')
+ .send(query)
+ .expect(200);
+ expect(body.hits.total.value).to.eql(3);
+ });
+
+ it('should filter out alerts with user who has access to security solution privileges and document level security', async () => {
+ const query = {
+ query: {
+ bool: {
+ should: [{ match_all: {} }],
+ },
+ },
+ };
+ const { body } = await supertestWithoutAuth
+ .post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
+ .auth(userAllSecWithDls.username, userAllSecWithDls.password)
+ .set('kbn-xsrf', 'true')
+ .send(query)
+ .expect(200);
+ expect(body.hits.total.value).to.eql(0);
+ });
+ });
+};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_calculation.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_calculation.ts
index 03f7734e42628..d4f5be1eff225 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_calculation.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_calculation.ts
@@ -23,6 +23,9 @@ import {
readRiskScores,
normalizeScores,
waitForRiskScoresToBePresent,
+ assetCriticalityRouteHelpersFactory,
+ cleanAssetCriticality,
+ waitForAssetCriticalityToBePresent,
} from '../../utils';
import { FtrProviderContext } from '../../../../ftr_provider_context';
@@ -116,17 +119,17 @@ export default ({ getService }: FtrProviderContext): void => {
const scores = await readRiskScores(es);
expect(scores.length).to.eql(1);
- expect(normalizeScores(scores)).to.eql([
- {
- calculated_level: 'Unknown',
- calculated_score: 21,
- calculated_score_norm: 8.039816232771823,
- category_1_score: 21,
- category_1_count: 1,
- id_field: 'host.name',
- id_value: 'host-1',
- },
- ]);
+ const [score] = normalizeScores(scores);
+
+ expect(score).to.eql({
+ calculated_level: 'Unknown',
+ calculated_score: 21,
+ calculated_score_norm: 8.039816232771823,
+ category_1_score: 8.039816232771821,
+ category_1_count: 1,
+ id_field: 'host.name',
+ id_value: 'host-1',
+ });
});
describe('paging through calculations', () => {
@@ -269,6 +272,60 @@ export default ({ getService }: FtrProviderContext): void => {
expect(scores.length).to.eql(10);
});
});
+
+ describe('@skipInServerless with asset criticality data', () => {
+ const assetCriticalityRoutes = assetCriticalityRouteHelpersFactory(supertest);
+
+ beforeEach(async () => {
+ await assetCriticalityRoutes.upsert({
+ id_field: 'host.name',
+ id_value: 'host-1',
+ criticality_level: 'important',
+ });
+ });
+
+ afterEach(async () => {
+ await cleanAssetCriticality({ log, es });
+ });
+
+ it('calculates and persists risk scores with additional criticality metadata and modifiers', async () => {
+ const documentId = uuidv4();
+ await indexListOfDocuments([buildDocument({ host: { name: 'host-1' } }, documentId)]);
+ await waitForAssetCriticalityToBePresent({ es, log });
+
+ const results = await calculateRiskScoreAfterRuleCreationAndExecution(documentId);
+ expect(results).to.eql({
+ after_keys: { host: { 'host.name': 'host-1' } },
+ errors: [],
+ scores_written: 1,
+ });
+
+ await waitForRiskScoresToBePresent({ es, log });
+ const scores = await readRiskScores(es);
+ expect(scores.length).to.eql(1);
+
+ const [score] = normalizeScores(scores);
+ expect(score).to.eql({
+ criticality_level: 'important',
+ criticality_modifier: 1.5,
+ calculated_level: 'Unknown',
+ calculated_score: 21,
+ calculated_score_norm: 11.59366948840633,
+ category_1_score: 8.039816232771821,
+ category_1_count: 1,
+ id_field: 'host.name',
+ id_value: 'host-1',
+ });
+ const [rawScore] = scores;
+
+ expect(
+ rawScore.host?.risk.category_1_score! + rawScore.host?.risk.category_2_score!
+ ).to.be.within(
+ score.calculated_score_norm! - 0.000000000000001,
+ score.calculated_score_norm! + 0.000000000000001
+ );
+ });
+ });
});
});
};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_preview.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_preview.ts
index e4284cdb5b837..bfb415bac02a8 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_preview.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_score_preview.ts
@@ -18,10 +18,13 @@ import {
dataGeneratorFactory,
} from '../../../detections_response/utils';
import {
+ assetCriticalityRouteHelpersFactory,
buildDocument,
+ cleanAssetCriticality,
createAndSyncRuleAndAlertsFactory,
deleteAllRiskScores,
sanitizeScores,
+ waitForAssetCriticalityToBePresent,
} from '../../utils';
import { FtrProviderContext } from '../../../../ftr_provider_context';
@@ -99,18 +102,23 @@ export default ({ getService }: FtrProviderContext): void => {
await indexListOfDocuments([buildDocument({ host: { name: 'host-1' } }, documentId)]);
const body = await getRiskScoreAfterRuleCreationAndExecution(documentId);
+ const [score] = sanitizeScores(body.scores.host!);
+ const [rawScore] = body.scores.host!;
+
+ expect(score).to.eql({
+ calculated_level: 'Unknown',
+ calculated_score: 21,
+ calculated_score_norm: 8.039816232771823,
+ category_1_count: 1,
+ category_1_score: 8.039816232771821,
+ id_field: 'host.name',
+ id_value: 'host-1',
+ });
- expect(sanitizeScores(body.scores.host!)).to.eql([
- {
- calculated_level: 'Unknown',
- calculated_score: 21,
- calculated_score_norm: 8.039816232771823,
- category_1_count: 1,
- category_1_score: 21,
- id_field: 'host.name',
- id_value: 'host-1',
- },
- ]);
+ expect(rawScore.category_1_score! + rawScore.category_2_score!).to.be.within(
+ score.calculated_score_norm! - 0.000000000000001,
+ score.calculated_score_norm! + 0.000000000000001
+ );
});
it('calculates risk from two alerts, each representing a unique host', async () => {
@@ -130,7 +138,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 21,
calculated_score_norm: 8.039816232771823,
category_1_count: 1,
- category_1_score: 21,
+ category_1_score: 8.039816232771821,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -139,7 +147,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 21,
calculated_score_norm: 8.039816232771823,
category_1_count: 1,
- category_1_score: 21,
+ category_1_score: 8.039816232771821,
id_field: 'host.name',
id_value: 'host-2',
},
@@ -163,7 +171,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 28.42462120245875,
calculated_score_norm: 10.88232052161514,
category_1_count: 2,
- category_1_score: 28,
+ category_1_score: 10.882320521615142,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -185,7 +193,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 47.25513506055279,
calculated_score_norm: 18.091552473412246,
category_1_count: 30,
- category_1_score: 37,
+ category_1_score: 18.091552473412246,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -210,7 +218,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 47.25513506055279,
calculated_score_norm: 18.091552473412246,
category_1_count: 30,
- category_1_score: 37,
+ category_1_score: 18.091552473412246,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -219,7 +227,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 21,
calculated_score_norm: 8.039816232771823,
category_1_count: 1,
- category_1_score: 21,
+ category_1_score: 8.039816232771821,
id_field: 'host.name',
id_value: 'host-2',
},
@@ -241,7 +249,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 50.67035607277805,
calculated_score_norm: 19.399064346392823,
category_1_count: 100,
- category_1_score: 37,
+ category_1_score: 19.399064346392823,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -266,7 +274,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 241.2874098703716,
calculated_score_norm: 92.37649688758484,
category_1_count: 100,
- category_1_score: 209,
+ category_1_score: 92.37649688758484,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -297,7 +305,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 254.91456029175757,
calculated_score_norm: 97.59362951445543,
category_1_count: 1000,
- category_1_score: 209,
+ category_1_score: 97.59362951445543,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -393,7 +401,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 225.1106801442913,
calculated_score_norm: 86.18326192354185,
category_1_count: 100,
- category_1_score: 203,
+ category_1_score: 86.18326192354185,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -422,7 +430,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 120.6437049351858,
calculated_score_norm: 46.18824844379242,
category_1_count: 100,
- category_1_score: 209,
+ category_1_score: 92.37649688758484,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -449,7 +457,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 168.9011869092601,
calculated_score_norm: 64.66354782130938,
category_1_count: 100,
- category_1_score: 209,
+ category_1_score: 92.37649688758484,
id_field: 'user.name',
id_value: 'user-1',
},
@@ -478,7 +486,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 93.23759116471251,
calculated_score_norm: 35.695861854790394,
category_1_count: 50,
- category_1_score: 209,
+ category_1_score: 89.23965463697598,
id_field: 'host.name',
id_value: 'host-1',
},
@@ -490,7 +498,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_score: 186.47518232942502,
calculated_score_norm: 71.39172370958079,
category_1_count: 50,
- category_1_score: 209,
+ category_1_score: 89.23965463697598,
id_field: 'user.name',
id_value: 'user-1',
},
@@ -527,7 +535,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_level: 'Low',
calculated_score: 93.2375911647125,
calculated_score_norm: 35.695861854790394,
- category_1_score: 77,
+ category_1_score: 35.69586185479039,
category_1_count: 50,
id_field: 'host.name',
id_value: 'host-1',
@@ -539,7 +547,7 @@ export default ({ getService }: FtrProviderContext): void => {
calculated_level: 'High',
calculated_score: 186.475182329425,
calculated_score_norm: 71.39172370958079,
- category_1_score: 165,
+ category_1_score: 71.39172370958077,
category_1_count: 50,
id_field: 'user.name',
id_value: 'user-1',
@@ -547,6 +555,58 @@ export default ({ getService }: FtrProviderContext): void => {
]);
});
});
+
+ describe('@skipInServerless with asset criticality data', () => {
+ const assetCriticalityRoutes = assetCriticalityRouteHelpersFactory(supertest);
+
+ beforeEach(async () => {
+ await assetCriticalityRoutes.upsert({
+ id_field: 'host.name',
+ id_value: 'host-1',
+ criticality_level: 'very_important',
+ });
+ });
+
+ afterEach(async () => {
+ await cleanAssetCriticality({ log, es });
+ });
+
+ it('calculates and persists risk scores with additional criticality metadata and modifiers', async () => {
+ const documentId = uuidv4();
+ await indexListOfDocuments([
+ buildDocument({ host: { name: 'host-1' } }, documentId),
+ buildDocument({ host: { name: 'host-2' } }, documentId),
+ ]);
+ await waitForAssetCriticalityToBePresent({ es, log });
+
+ const body = await getRiskScoreAfterRuleCreationAndExecution(documentId, {
+ alerts: 2,
+ });
+
+ expect(sanitizeScores(body.scores.host!)).to.eql([
+ {
+ criticality_level: 'very_important',
+ criticality_modifier: 2.0,
+ calculated_level: 'Unknown',
+ calculated_score: 21,
+ calculated_score_norm: 14.8830616583983,
+ category_1_count: 1,
+ category_1_score: 8.039816232771821,
+ id_field: 'host.name',
+ id_value: 'host-1',
+ },
+ {
+ calculated_level: 'Unknown',
+ calculated_score: 21,
+ calculated_score_norm: 8.039816232771823,
+ category_1_count: 1,
+ category_1_score: 8.039816232771821,
+ id_field: 'host.name',
+ id_value: 'host-2',
+ },
+ ]);
+ });
+ });
});
});
};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution.ts
index 7ebfb568e8cd3..1f9c886b932f8 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/default_license/risk_engine/risk_scoring_task/task_execution.ts
@@ -23,6 +23,9 @@ import {
getRiskEngineTask,
waitForRiskEngineTaskToBeGone,
cleanRiskEngine,
+ assetCriticalityRouteHelpersFactory,
+ cleanAssetCriticality,
+ waitForAssetCriticalityToBePresent,
} from '../../../utils';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
@@ -157,7 +160,8 @@ export default ({ getService }: FtrProviderContext): void => {
await riskEngineRoutes.disable();
});
- describe('when task interval is modified', () => {
+ // Temporary, expected failure: See https://github.com/elastic/security-team/issues/8012
+ describe.skip('when task interval is modified', () => {
beforeEach(async () => {
await updateRiskEngineConfigSO({
attributes: {
@@ -179,8 +183,7 @@ export default ({ getService }: FtrProviderContext): void => {
});
});
- // FLAKY: https://github.com/elastic/kibana/issues/171132
- describe.skip('with some alerts containing hosts and others containing users', () => {
+ describe('with some alerts containing hosts and others containing users', () => {
let hostId: string;
let userId: string;
@@ -212,20 +215,68 @@ export default ({ getService }: FtrProviderContext): void => {
alerts: 20,
riskScore: 40,
});
-
- await riskEngineRoutes.init();
});
it('@skipInQA calculates and persists risk scores for both types of entities', async () => {
+ await riskEngineRoutes.init();
await waitForRiskScoresToBePresent({ es, log, scoreCount: 20 });
const riskScores = await readRiskScores(es);
- expect(riskScores.length).to.eql(20);
+ expect(riskScores.length).to.be.greaterThan(0);
const scoredIdentifiers = normalizeScores(riskScores).map(
({ id_field: idField }) => idField
);
- expect(scoredIdentifiers.includes('host.name')).to.be(true);
- expect(scoredIdentifiers.includes('user.name')).to.be(true);
+ expect(scoredIdentifiers).to.contain('host.name');
+ expect(scoredIdentifiers).to.contain('user.name');
+ });
+
+ context('@skipInServerless with asset criticality data', () => {
+ const assetCriticalityRoutes = assetCriticalityRouteHelpersFactory(supertest);
+
+ beforeEach(async () => {
+ await assetCriticalityRoutes.upsert({
+ id_field: 'host.name',
+ id_value: 'host-1',
+ criticality_level: 'very_important',
+ });
+ });
+
+ afterEach(async () => {
+ await cleanAssetCriticality({ log, es });
+ });
+
+ it('calculates risk scores with asset criticality data', async () => {
+ await waitForAssetCriticalityToBePresent({ es, log });
+ await riskEngineRoutes.init();
+ await waitForRiskScoresToBePresent({ es, log, scoreCount: 20 });
+ const riskScores = await readRiskScores(es);
+
+ expect(riskScores.length).to.be.greaterThan(0);
+ const assetCriticalityLevels = riskScores.map(
+ (riskScore) => riskScore.host?.risk.criticality_level
+ );
+ const assetCriticalityModifiers = riskScores.map(
+ (riskScore) => riskScore.host?.risk.criticality_modifier
+ );
+
+ expect(assetCriticalityLevels).to.contain('very_important');
+ expect(assetCriticalityModifiers).to.contain(2);
+
+ const scoreWithCriticality = riskScores.find((score) => score.host?.name === 'host-1');
+ expect(normalizeScores([scoreWithCriticality!])).to.eql([
+ {
+ id_field: 'host.name',
+ id_value: 'host-1',
+ criticality_level: 'very_important',
+ criticality_modifier: 2,
+ calculated_level: 'Moderate',
+ calculated_score: 79.81345973382406,
+ calculated_score_norm: 46.809565696393314,
+ category_1_count: 10,
+ category_1_score: 30.55645472198471,
+ },
+ ]);
+ });
});
});
});
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts
index b745d1d0f705f..6abcb908f6083 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/asset_criticality.ts
@@ -15,10 +15,11 @@ import {
ASSET_CRITICALITY_URL,
ASSET_CRITICALITY_PRIVILEGES_URL,
} from '@kbn/security-solution-plugin/common/constants';
+import type { AssetCriticalityRecord } from '@kbn/security-solution-plugin/common/api/entity_analytics';
import type { Client } from '@elastic/elasticsearch';
import type { ToolingLog } from '@kbn/tooling-log';
import querystring from 'querystring';
-import { routeWithNamespace } from '../../detections_response/utils';
+import { routeWithNamespace, waitFor } from '../../detections_response/utils';
export const getAssetCriticalityIndex = (namespace?: string) =>
`.asset-criticality.asset-criticality-${namespace ?? 'default'}`;
@@ -123,3 +124,51 @@ export const assetCriticalityRouteHelpersFactoryNoAuth = (
.send()
.expect(200),
});
+
+/**
+ * Function to read asset criticality records from ES. By default, it reads from the asset criticality index in the default space, but this can be overridden with the
+ * `index` parameter.
+ *
+ * @param {string[]} index - the index or indices to read criticality from.
+ * @param {number} size - the size parameter of the query
+ */
+export const readAssetCriticality = async (
+ es: Client,
+ index: string[] = [getAssetCriticalityIndex()],
+ size: number = 1000
+): Promise => {
+ const results = await es.search({
+ index,
+ size,
+ });
+ return results.hits.hits.map((hit) => hit._source as AssetCriticalityRecord);
+};
+
+/**
+ * Function to read asset criticality from ES and wait for them to be
+ * present/readable. By default, it reads from the asset criticality index in the
+ * default space, but this can be overridden with the `index` parameter.
+ *
+ * @param {string[]} index - the index or indices to read asset criticality from.
+ * @param {number} docCount - the number of asset criticality docs to wait for. Defaults to 1.
+ */
+export const waitForAssetCriticalityToBePresent = async ({
+ es,
+ log,
+ index = [getAssetCriticalityIndex()],
+ docCount = 1,
+}: {
+ es: Client;
+ log: ToolingLog;
+ index?: string[];
+ docCount?: number;
+}): Promise => {
+ await waitFor(
+ async () => {
+ const criticalities = await readAssetCriticality(es, index, docCount + 10);
+ return criticalities.length >= docCount;
+ },
+ 'waitForAssetCriticalityToBePresent',
+ log
+ );
+};
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/index.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/index.ts
index dacdf5052c912..7ff049a997da1 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/index.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/index.ts
@@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+
export * from './risk_engine';
export * from './get_risk_engine_stats';
export * from './asset_criticality';
diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts
index 5a29900f5e8d6..fb242e72bc898 100644
--- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts
+++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/utils/risk_engine.ts
@@ -37,11 +37,15 @@ import {
} from '../../detections_response/utils';
const sanitizeScore = (score: Partial): Partial => {
- delete score['@timestamp'];
- delete score.inputs;
- delete score.notes;
- // delete score.category_1_score;
- return score;
+ const {
+ '@timestamp': timestamp,
+ inputs,
+ notes,
+ category_2_count: cat2Count,
+ category_2_score: cat2Score,
+ ...rest
+ } = score;
+ return rest;
};
export const sanitizeScores = (scores: Array>): Array> =>
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/filters.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/filters.cy.ts
new file mode 100644
index 0000000000000..d87639a231f58
--- /dev/null
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/timelines/filters.cy.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { login } from '../../../tasks/login';
+import { visitWithTimeRange } from '../../../tasks/navigation';
+import {
+ createNewTimeline,
+ addNameAndDescriptionToTimeline,
+ populateTimeline,
+} from '../../../tasks/timeline';
+import { openTimelineUsingToggle } from '../../../tasks/security_main';
+import { ALERTS_URL } from '../../../urls/navigation';
+import { getTimeline } from '../../../objects/timeline';
+import {
+ GET_TIMELINE_GRID_CELL,
+ TIMELINE_FILTER_FOR,
+ TIMELINE_FILTER_OUT,
+ TIMELINE_EVENT,
+ TIMELINE_FILTER_BADGE_ENABLED,
+} from '../../../screens/timeline';
+
+describe(
+ `timleine cell actions`,
+ {
+ tags: ['@ess'],
+ },
+ () => {
+ beforeEach(() => {
+ login();
+ visitWithTimeRange(ALERTS_URL);
+ openTimelineUsingToggle();
+ createNewTimeline();
+ addNameAndDescriptionToTimeline(getTimeline());
+ populateTimeline();
+ });
+ it('filter in', () => {
+ cy.get(GET_TIMELINE_GRID_CELL('event.category')).trigger('mouseover');
+ cy.get(TIMELINE_FILTER_FOR).should('be.visible').click();
+ cy.get(TIMELINE_FILTER_BADGE_ENABLED).should('be.visible');
+ });
+
+ it('filter out', () => {
+ cy.get(GET_TIMELINE_GRID_CELL('event.category')).trigger('mouseover');
+ cy.get(TIMELINE_FILTER_OUT).should('be.visible').click();
+ cy.get(TIMELINE_EVENT).should('not.exist');
+ });
+ }
+);
diff --git a/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts b/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts
index b498d3d6bc32b..2794a111e94c2 100644
--- a/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts
+++ b/x-pack/test/security_solution_cypress/cypress/screens/timeline.ts
@@ -284,6 +284,12 @@ export const HOVER_ACTIONS = {
SHOW_TOP: '[data-test-subj=show-top-field]',
};
+export const TIMELINE_FILTER_OUT = '[data-test-subj="filter-out-value"]';
+
+export const TIMELINE_FILTER_FOR = '[data-test-subj="filter-for-value"]';
+
+export const TIMELINE_FILTER_BADGE_ENABLED = '[data-test-subj~="filter-enabled"]';
+
export const GET_TIMELINE_HEADER = (fieldName: string) => {
return `[data-test-subj="timeline"] [data-test-subj="header-text-${fieldName}"]`;
};
diff --git a/x-pack/test/security_solution_endpoint/apps/integrations/endpoint_exceptions.ts b/x-pack/test/security_solution_endpoint/apps/integrations/endpoint_exceptions.ts
index 539101c795047..8647d418a6395 100644
--- a/x-pack/test/security_solution_endpoint/apps/integrations/endpoint_exceptions.ts
+++ b/x-pack/test/security_solution_endpoint/apps/integrations/endpoint_exceptions.ts
@@ -11,7 +11,7 @@ import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/com
import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants';
import { ArtifactElasticsearchProperties } from '@kbn/fleet-plugin/server/services';
import { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../ftr_provider_context';
import { targetTags } from '../../target_tags';
diff --git a/x-pack/test/security_solution_endpoint/apps/integrations/trusted_apps_list.ts b/x-pack/test/security_solution_endpoint/apps/integrations/trusted_apps_list.ts
index 1d48a415b1577..e00dde08d58fb 100644
--- a/x-pack/test/security_solution_endpoint/apps/integrations/trusted_apps_list.ts
+++ b/x-pack/test/security_solution_endpoint/apps/integrations/trusted_apps_list.ts
@@ -16,8 +16,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const browser = getService('browser');
const endpointTestResources = getService('endpointTestResources');
- // FLAKY: https://github.com/elastic/kibana/issues/171481
- describe.skip('When on the Trusted Apps list', function () {
+ describe('When on the Trusted Apps list', function () {
targetTags(this, ['@ess', '@serverless']);
let indexedData: IndexedHostsAndAlertsResponse;
@@ -34,8 +33,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.missingOrFail('header-page-title');
});
- // FLAKY: https://github.com/elastic/kibana/issues/171481
- it.skip('should be able to add a new trusted app and remove it', async () => {
+ it('should be able to add a new trusted app and remove it', async () => {
const SHA256 = 'A4370C0CF81686C0B696FA6261c9d3e0d810ae704ab8301839dffd5d5112f476';
// Add it
diff --git a/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts b/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts
index ec3eaf97eb81b..ceea71593a95e 100644
--- a/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts
+++ b/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
export function EndpointPageProvider({ getService, getPageObjects }: FtrProviderContext) {
diff --git a/x-pack/test/security_solution_endpoint/page_objects/ingest_manager_create_package_policy_page.ts b/x-pack/test/security_solution_endpoint/page_objects/ingest_manager_create_package_policy_page.ts
index 913d1e18d1dc7..acdb58460cbbd 100644
--- a/x-pack/test/security_solution_endpoint/page_objects/ingest_manager_create_package_policy_page.ts
+++ b/x-pack/test/security_solution_endpoint/page_objects/ingest_manager_create_package_policy_page.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
export function IngestManagerCreatePackagePolicy({
getService,
diff --git a/x-pack/test/security_solution_endpoint/page_objects/page_utils.ts b/x-pack/test/security_solution_endpoint/page_objects/page_utils.ts
index 69cb8e455b152..d14846f8583ed 100644
--- a/x-pack/test/security_solution_endpoint/page_objects/page_utils.ts
+++ b/x-pack/test/security_solution_endpoint/page_objects/page_utils.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
-import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
export function EndpointPageUtils({ getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
diff --git a/x-pack/test/security_solution_ftr/page_objects/detections/index.ts b/x-pack/test/security_solution_ftr/page_objects/detections/index.ts
index a8ff43a8e06bf..e45fa7f7a5eb7 100644
--- a/x-pack/test/security_solution_ftr/page_objects/detections/index.ts
+++ b/x-pack/test/security_solution_ftr/page_objects/detections/index.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrService } from '../../../functional/ftr_provider_context';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
const ALERT_TABLE_ROW_CSS_SELECTOR = '[data-test-subj="alertsTable"] .euiDataGridRow';
diff --git a/x-pack/test/security_solution_ftr/page_objects/hosts/index.ts b/x-pack/test/security_solution_ftr/page_objects/hosts/index.ts
index 0d80db3141214..50e232a8693e1 100644
--- a/x-pack/test/security_solution_ftr/page_objects/hosts/index.ts
+++ b/x-pack/test/security_solution_ftr/page_objects/hosts/index.ts
@@ -5,8 +5,8 @@
* 2.0.
*/
+import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrService } from '../../../functional/ftr_provider_context';
-import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
export class HostsPageObject extends FtrService {
private readonly pageObjects = this.ctx.getPageObjects(['common', 'header']);
diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json
index 894d4c8039946..4ca836a475546 100644
--- a/x-pack/test/tsconfig.json
+++ b/x-pack/test/tsconfig.json
@@ -165,5 +165,6 @@
"@kbn/log-explorer-plugin",
"@kbn/security-plugin-types-common",
"@kbn/typed-react-router-config",
+ "@kbn/ftr-common-functional-ui-services",
]
}
diff --git a/x-pack/test/upgrade/services/rules_upgrade_services.ts b/x-pack/test/upgrade/services/rules_upgrade_services.ts
index 188f41bcddc9d..54780fbe5adfb 100644
--- a/x-pack/test/upgrade/services/rules_upgrade_services.ts
+++ b/x-pack/test/upgrade/services/rules_upgrade_services.ts
@@ -6,11 +6,8 @@
*/
import expect from '@kbn/expect';
+import type { CustomCheerio, CustomCheerioStatic } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
-import {
- CustomCheerio,
- CustomCheerioStatic,
-} from '../../../../test/functional/services/lib/web_element_wrapper/custom_cheerio_api';
export function RulesHelper({ getPageObjects, getService }: FtrProviderContext) {
const find = getService('find');
diff --git a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts
index 93b5dea4f0495..9a91f5da1240d 100644
--- a/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts
+++ b/x-pack/test_serverless/functional/page_objects/svl_common_navigation.ts
@@ -16,8 +16,8 @@ import type { NavigationID as DevNavId } from '@kbn/default-nav-devtools';
// use this for nicer type suggestions, but allow any string anyway
type NavigationId = MlNavId | AlNavId | MgmtNavId | DevNavId | string;
+import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import type { FtrProviderContext } from '../ftr_provider_context';
-import type { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
const getSectionIdTestSubj = (sectionId: NavigationId) => `~nav-item-${sectionId}`;
diff --git a/x-pack/test_serverless/functional/page_objects/svl_triggers_actions_ui_page.ts b/x-pack/test_serverless/functional/page_objects/svl_triggers_actions_ui_page.ts
index 589544cca9c5e..e9f407c06753f 100644
--- a/x-pack/test_serverless/functional/page_objects/svl_triggers_actions_ui_page.ts
+++ b/x-pack/test_serverless/functional/page_objects/svl_triggers_actions_ui_page.ts
@@ -6,10 +6,7 @@
*/
import expect from '@kbn/expect';
-import type {
- CustomCheerio,
- CustomCheerioStatic,
-} from '../../../../test/functional/services/lib/web_element_wrapper/custom_cheerio_api';
+import type { CustomCheerio, CustomCheerioStatic } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../ftr_provider_context';
const ENTER_KEY = '\uE007';
diff --git a/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/visualize_field.ts b/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/visualize_field.ts
index ab1ad279b886b..e412fea58df57 100644
--- a/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/visualize_field.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/discover/x_pack/visualize_field.ts
@@ -6,7 +6,7 @@
*/
import expect from '@kbn/expect';
-import type { WebElementWrapper } from '../../../../../../../test/functional/services/lib/web_element_wrapper';
+import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
diff --git a/x-pack/test_serverless/functional/test_suites/common/examples/search/warnings.ts b/x-pack/test_serverless/functional/test_suites/common/examples/search/warnings.ts
index a254cb753c864..43ec250ff9967 100644
--- a/x-pack/test_serverless/functional/test_suites/common/examples/search/warnings.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/examples/search/warnings.ts
@@ -8,7 +8,7 @@
import type { estypes } from '@elastic/elasticsearch';
import expect from '@kbn/expect';
import assert from 'assert';
-import type { WebElementWrapper } from '../../../../../../../test/functional/services/lib/web_element_wrapper';
+import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import type { FtrProviderContext } from '../../../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
diff --git a/x-pack/test_serverless/functional/test_suites/common/management/data_views/_runtime_fields.ts b/x-pack/test_serverless/functional/test_suites/common/management/data_views/_runtime_fields.ts
index f900329539e69..b7bc99d9aecb1 100644
--- a/x-pack/test_serverless/functional/test_suites/common/management/data_views/_runtime_fields.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/management/data_views/_runtime_fields.ts
@@ -16,7 +16,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['settings', 'common']);
const testSubjects = getService('testSubjects');
- describe('runtime fields', function () {
+ // Failing: See https://github.com/elastic/kibana/issues/173558
+ describe.skip('runtime fields', function () {
this.tags(['skipFirefox']);
before(async function () {
diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index.ts b/x-pack/test_serverless/functional/test_suites/common/management/index.ts
index 3291f50dc6bc8..e523e27c3cf00 100644
--- a/x-pack/test_serverless/functional/test_suites/common/management/index.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/management/index.ts
@@ -9,11 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default ({ loadTestFile }: FtrProviderContext) => {
describe('Serverless Common UI - Management', function () {
- loadTestFile(require.resolve('./index_management/index_templates'));
- loadTestFile(require.resolve('./index_management/indices'));
- loadTestFile(require.resolve('./index_management/create_enrich_policy'));
- loadTestFile(require.resolve('./index_management/enrich_policies'));
- loadTestFile(require.resolve('./index_management/component_templates'));
+ loadTestFile(require.resolve('./index_management'));
loadTestFile(require.resolve('./transforms/search_bar_features'));
loadTestFile(require.resolve('./transforms/transform_list'));
loadTestFile(require.resolve('./advanced_settings'));
diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/component_templates.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/component_templates.ts
index 9e044667692cf..e70470582b2b5 100644
--- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/component_templates.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/component_templates.ts
@@ -7,8 +7,8 @@
import expect from '@kbn/expect';
+import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../../../ftr_provider_context';
-import type { WebElementWrapper } from '../../../../../../../test/functional/services/lib/web_element_wrapper';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['svlCommonPage', 'common', 'indexManagement', 'header']);
diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/data_streams.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/data_streams.ts
index bbf55e359d360..4bd839e2c9ebb 100644
--- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/data_streams.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/data_streams.ts
@@ -88,7 +88,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('shows the details flyout when clicking on a data stream', async () => {
// Open details flyout
- await pageObjects.indexManagement.clickDataStreamAt(0);
+ await pageObjects.indexManagement.clickDataStreamNameLink(TEST_DS_NAME);
// Verify url is stateful
const url = await browser.getCurrentUrl();
expect(url).to.contain(`/data_streams/${TEST_DS_NAME}`);
@@ -100,7 +100,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('allows to update data retention', async () => {
// Open details flyout
- await pageObjects.indexManagement.clickDataStreamAt(0);
+ await pageObjects.indexManagement.clickDataStreamNameLink(TEST_DS_NAME);
// Open the edit retention dialog
await testSubjects.click('manageDataStreamButton');
await testSubjects.click('editDataRetentionButton');
@@ -122,7 +122,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('allows to disable data retention', async () => {
// Open details flyout
- await pageObjects.indexManagement.clickDataStreamAt(0);
+ await pageObjects.indexManagement.clickDataStreamNameLink(TEST_DS_NAME);
// Open the edit retention dialog
await testSubjects.click('manageDataStreamButton');
await testSubjects.click('editDataRetentionButton');
diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/index.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/index.ts
new file mode 100644
index 0000000000000..0df628a2ba587
--- /dev/null
+++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/index.ts
@@ -0,0 +1,19 @@
+/*
+ * 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 { FtrProviderContext } from '../../../../ftr_provider_context';
+
+export default ({ loadTestFile }: FtrProviderContext) => {
+ describe('Index Management', function () {
+ loadTestFile(require.resolve('./component_templates'));
+ loadTestFile(require.resolve('./create_enrich_policy'));
+ loadTestFile(require.resolve('./data_streams'));
+ loadTestFile(require.resolve('./enrich_policies'));
+ loadTestFile(require.resolve('./index_templates'));
+ loadTestFile(require.resolve('./indices'));
+ });
+};
diff --git a/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_templates.ts b/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_templates.ts
index 791d3c313b7ab..371ee4debe98f 100644
--- a/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_templates.ts
+++ b/x-pack/test_serverless/functional/test_suites/common/management/index_management/index_templates.ts
@@ -7,8 +7,8 @@
import expect from '@kbn/expect';
+import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import { FtrProviderContext } from '../../../../ftr_provider_context';
-import type { WebElementWrapper } from '../../../../../../../test/functional/services/lib/web_element_wrapper';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['svlCommonPage', 'common', 'indexManagement', 'header']);
diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts
index 7e23b43b253c7..92dfb107dd440 100644
--- a/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts
+++ b/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts
@@ -7,7 +7,7 @@
import moment from 'moment';
import expect from '@kbn/expect';
-import type { WebElementWrapper } from '../../../../../../test/functional/services/lib/web_element_wrapper';
+import type { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services';
import type { FtrProviderContext } from '../../../ftr_provider_context';
import { HOSTS_VIEW_PATH } from './constants';
diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json
index 54c1e26f069d1..c9c37a3c3f3a1 100644
--- a/x-pack/test_serverless/tsconfig.json
+++ b/x-pack/test_serverless/tsconfig.json
@@ -84,5 +84,6 @@
"@kbn/log-explorer-plugin",
"@kbn/index-management-plugin",
"@kbn/alerting-plugin",
+ "@kbn/ftr-common-functional-ui-services",
]
}