mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Maps] Use SO-references for geo-containment alerts (#114559)
This commit is contained in:
parent
411886ac8b
commit
712fac6042
9 changed files with 428 additions and 27 deletions
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { extractEntityAndBoundaryReferences } from './migrations';
|
||||
|
||||
describe('geo_containment migration utilities', () => {
|
||||
test('extractEntityAndBoundaryReferences', () => {
|
||||
expect(
|
||||
extractEntityAndBoundaryReferences({
|
||||
index: 'foo*',
|
||||
indexId: 'foobar',
|
||||
geoField: 'geometry',
|
||||
entity: 'vehicle_id',
|
||||
dateField: '@timestamp',
|
||||
boundaryType: 'entireIndex',
|
||||
boundaryIndexTitle: 'boundary*',
|
||||
boundaryIndexId: 'boundaryid',
|
||||
boundaryGeoField: 'geometry',
|
||||
})
|
||||
).toEqual({
|
||||
params: {
|
||||
boundaryGeoField: 'geometry',
|
||||
boundaryIndexRefName: 'boundary_index_boundaryid',
|
||||
boundaryIndexTitle: 'boundary*',
|
||||
boundaryType: 'entireIndex',
|
||||
dateField: '@timestamp',
|
||||
entity: 'vehicle_id',
|
||||
geoField: 'geometry',
|
||||
index: 'foo*',
|
||||
indexRefName: 'tracked_index_foobar',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: 'foobar',
|
||||
name: 'param:tracked_index_foobar',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
{
|
||||
id: 'boundaryid',
|
||||
name: 'param:boundary_index_boundaryid',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 {
|
||||
SavedObjectAttributes,
|
||||
SavedObjectReference,
|
||||
SavedObjectUnsanitizedDoc,
|
||||
} from 'kibana/server';
|
||||
import { AlertTypeParams } from '../../index';
|
||||
import { Query } from '../../../../../../src/plugins/data/common/query';
|
||||
import { RawAlert } from '../../types';
|
||||
|
||||
// These definitions are dupes of the SO-types in stack_alerts/geo_containment
|
||||
// There are not exported to avoid deep imports from stack_alerts plugins into here
|
||||
const GEO_CONTAINMENT_ID = '.geo-containment';
|
||||
interface GeoContainmentParams extends AlertTypeParams {
|
||||
index: string;
|
||||
indexId: string;
|
||||
geoField: string;
|
||||
entity: string;
|
||||
dateField: string;
|
||||
boundaryType: string;
|
||||
boundaryIndexTitle: string;
|
||||
boundaryIndexId: string;
|
||||
boundaryGeoField: string;
|
||||
boundaryNameField?: string;
|
||||
indexQuery?: Query;
|
||||
boundaryIndexQuery?: Query;
|
||||
}
|
||||
type GeoContainmentExtractedParams = Omit<GeoContainmentParams, 'indexId' | 'boundaryIndexId'> & {
|
||||
indexRefName: string;
|
||||
boundaryIndexRefName: string;
|
||||
};
|
||||
|
||||
export function extractEntityAndBoundaryReferences(params: GeoContainmentParams): {
|
||||
params: GeoContainmentExtractedParams;
|
||||
references: SavedObjectReference[];
|
||||
} {
|
||||
const { indexId, boundaryIndexId, ...otherParams } = params;
|
||||
|
||||
const indexRefNamePrefix = 'tracked_index_';
|
||||
const boundaryRefNamePrefix = 'boundary_index_';
|
||||
|
||||
// Since these are stack-alerts, we need to prefix with the `param:`-namespace
|
||||
const references = [
|
||||
{
|
||||
name: `param:${indexRefNamePrefix}${indexId}`,
|
||||
type: `index-pattern`,
|
||||
id: indexId as string,
|
||||
},
|
||||
{
|
||||
name: `param:${boundaryRefNamePrefix}${boundaryIndexId}`,
|
||||
type: 'index-pattern',
|
||||
id: boundaryIndexId as string,
|
||||
},
|
||||
];
|
||||
return {
|
||||
params: {
|
||||
...otherParams,
|
||||
indexRefName: `${indexRefNamePrefix}${indexId}`,
|
||||
boundaryIndexRefName: `${boundaryRefNamePrefix}${boundaryIndexId}`,
|
||||
},
|
||||
references,
|
||||
};
|
||||
}
|
||||
|
||||
export function extractRefsFromGeoContainmentAlert(
|
||||
doc: SavedObjectUnsanitizedDoc<RawAlert>
|
||||
): SavedObjectUnsanitizedDoc<RawAlert> {
|
||||
if (doc.attributes.alertTypeId !== GEO_CONTAINMENT_ID) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
const {
|
||||
attributes: { params },
|
||||
} = doc;
|
||||
|
||||
const { params: newParams, references } = extractEntityAndBoundaryReferences(
|
||||
params as GeoContainmentParams
|
||||
);
|
||||
return {
|
||||
...doc,
|
||||
attributes: {
|
||||
...doc.attributes,
|
||||
params: newParams as SavedObjectAttributes,
|
||||
},
|
||||
references: [...(doc.references || []), ...references],
|
||||
};
|
||||
}
|
|
@ -1913,6 +1913,96 @@ describe('successful migrations', () => {
|
|||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('geo-containment alert migration extracts boundary and index references', () => {
|
||||
const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0'];
|
||||
const alert = {
|
||||
...getMockData({
|
||||
alertTypeId: '.geo-containment',
|
||||
params: {
|
||||
indexId: 'foo',
|
||||
boundaryIndexId: 'bar',
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const migratedAlert = migration7160(alert, migrationContext);
|
||||
|
||||
expect(migratedAlert.references).toEqual([
|
||||
{ id: 'foo', name: 'param:tracked_index_foo', type: 'index-pattern' },
|
||||
{ id: 'bar', name: 'param:boundary_index_bar', type: 'index-pattern' },
|
||||
]);
|
||||
|
||||
expect(migratedAlert.attributes.params).toEqual({
|
||||
boundaryIndexRefName: 'boundary_index_bar',
|
||||
indexRefName: 'tracked_index_foo',
|
||||
});
|
||||
|
||||
expect(migratedAlert.attributes.params.indexId).toEqual(undefined);
|
||||
expect(migratedAlert.attributes.params.boundaryIndexId).toEqual(undefined);
|
||||
});
|
||||
|
||||
test('geo-containment alert migration should preserve foreign references', () => {
|
||||
const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0'];
|
||||
const alert = {
|
||||
...getMockData({
|
||||
alertTypeId: '.geo-containment',
|
||||
params: {
|
||||
indexId: 'foo',
|
||||
boundaryIndexId: 'bar',
|
||||
},
|
||||
}),
|
||||
references: [
|
||||
{
|
||||
name: 'foreign-name',
|
||||
id: '999',
|
||||
type: 'foreign-name',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const migratedAlert = migration7160(alert, migrationContext);
|
||||
|
||||
expect(migratedAlert.references).toEqual([
|
||||
{
|
||||
name: 'foreign-name',
|
||||
id: '999',
|
||||
type: 'foreign-name',
|
||||
},
|
||||
{ id: 'foo', name: 'param:tracked_index_foo', type: 'index-pattern' },
|
||||
{ id: 'bar', name: 'param:boundary_index_bar', type: 'index-pattern' },
|
||||
]);
|
||||
|
||||
expect(migratedAlert.attributes.params).toEqual({
|
||||
boundaryIndexRefName: 'boundary_index_bar',
|
||||
indexRefName: 'tracked_index_foo',
|
||||
});
|
||||
|
||||
expect(migratedAlert.attributes.params.indexId).toEqual(undefined);
|
||||
expect(migratedAlert.attributes.params.boundaryIndexId).toEqual(undefined);
|
||||
});
|
||||
|
||||
test('geo-containment alert migration ignores other alert-types', () => {
|
||||
const migration7160 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['7.16.0'];
|
||||
const alert = {
|
||||
...getMockData({
|
||||
alertTypeId: '.foo',
|
||||
references: [
|
||||
{
|
||||
name: 'foreign-name',
|
||||
id: '999',
|
||||
type: 'foreign-name',
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
const migratedAlert = migration7160(alert, migrationContext);
|
||||
|
||||
expect(typeof migratedAlert.attributes.legacyId).toEqual('string'); // introduced by setLegacyId migration
|
||||
delete migratedAlert.attributes.legacyId;
|
||||
expect(migratedAlert).toEqual(alert);
|
||||
});
|
||||
});
|
||||
|
||||
describe('8.0.0', () => {
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
import { RawAlert, RawAlertAction } from '../types';
|
||||
import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server';
|
||||
import type { IsMigrationNeededPredicate } from '../../../encrypted_saved_objects/server';
|
||||
import { extractRefsFromGeoContainmentAlert } from './geo_containment/migrations';
|
||||
|
||||
const SIEM_APP_ID = 'securitySolution';
|
||||
const SIEM_SERVER_APP_ID = 'siem';
|
||||
|
@ -117,7 +118,8 @@ export function getMigrations(
|
|||
pipeMigrations(
|
||||
setLegacyId,
|
||||
getRemovePreconfiguredConnectorsFromReferencesFn(isPreconfigured),
|
||||
addRuleIdsToLegacyNotificationReferences
|
||||
addRuleIdsToLegacyNotificationReferences,
|
||||
extractRefsFromGeoContainmentAlert
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { Logger } from 'src/core/server';
|
||||
import { Logger, SavedObjectReference } from 'src/core/server';
|
||||
import { STACK_ALERTS_FEATURE_ID } from '../../../common';
|
||||
import { getGeoContainmentExecutor } from './geo_containment';
|
||||
import {
|
||||
|
@ -15,14 +15,37 @@ import {
|
|||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
RuleParamsAndRefs,
|
||||
AlertTypeParams,
|
||||
} from '../../../../alerting/server';
|
||||
import { Query } from '../../../../../../src/plugins/data/common/query';
|
||||
|
||||
export const GEO_CONTAINMENT_ID = '.geo-containment';
|
||||
export const ActionGroupId = 'Tracked entity contained';
|
||||
export const RecoveryActionGroupId = 'notGeoContained';
|
||||
|
||||
export const GEO_CONTAINMENT_ID = '.geo-containment';
|
||||
export interface GeoContainmentParams extends AlertTypeParams {
|
||||
index: string;
|
||||
indexId: string;
|
||||
geoField: string;
|
||||
entity: string;
|
||||
dateField: string;
|
||||
boundaryType: string;
|
||||
boundaryIndexTitle: string;
|
||||
boundaryIndexId: string;
|
||||
boundaryGeoField: string;
|
||||
boundaryNameField?: string;
|
||||
indexQuery?: Query;
|
||||
boundaryIndexQuery?: Query;
|
||||
}
|
||||
export type GeoContainmentExtractedParams = Omit<
|
||||
GeoContainmentParams,
|
||||
'indexId' | 'boundaryIndexId'
|
||||
> & {
|
||||
indexRefName: string;
|
||||
boundaryIndexRefName: string;
|
||||
};
|
||||
|
||||
const actionVariableContextEntityIdLabel = i18n.translate(
|
||||
'xpack.stackAlerts.geoContainment.actionVariableContextEntityIdLabel',
|
||||
{
|
||||
|
@ -103,20 +126,6 @@ export const ParamsSchema = schema.object({
|
|||
boundaryIndexQuery: schema.maybe(schema.any({})),
|
||||
});
|
||||
|
||||
export interface GeoContainmentParams extends AlertTypeParams {
|
||||
index: string;
|
||||
indexId: string;
|
||||
geoField: string;
|
||||
entity: string;
|
||||
dateField: string;
|
||||
boundaryType: string;
|
||||
boundaryIndexTitle: string;
|
||||
boundaryIndexId: string;
|
||||
boundaryGeoField: string;
|
||||
boundaryNameField?: string;
|
||||
indexQuery?: Query;
|
||||
boundaryIndexQuery?: Query;
|
||||
}
|
||||
export interface GeoContainmentState extends AlertTypeState {
|
||||
shapesFilters: Record<string, unknown>;
|
||||
shapesIdsNamesMap: Record<string, unknown>;
|
||||
|
@ -140,7 +149,7 @@ export interface GeoContainmentInstanceContext extends AlertInstanceContext {
|
|||
|
||||
export type GeoContainmentAlertType = AlertType<
|
||||
GeoContainmentParams,
|
||||
never, // Only use if defining useSavedObjectReferences hook
|
||||
GeoContainmentExtractedParams,
|
||||
GeoContainmentState,
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentInstanceContext,
|
||||
|
@ -148,6 +157,56 @@ export type GeoContainmentAlertType = AlertType<
|
|||
typeof RecoveryActionGroupId
|
||||
>;
|
||||
|
||||
export function extractEntityAndBoundaryReferences(params: GeoContainmentParams): {
|
||||
params: GeoContainmentExtractedParams;
|
||||
references: SavedObjectReference[];
|
||||
} {
|
||||
const { indexId, boundaryIndexId, ...otherParams } = params;
|
||||
|
||||
// Reference names omit the `param:`-prefix. This is handled by the alerting framework already
|
||||
const references = [
|
||||
{
|
||||
name: `tracked_index_${indexId}`,
|
||||
type: 'index-pattern',
|
||||
id: indexId as string,
|
||||
},
|
||||
{
|
||||
name: `boundary_index_${boundaryIndexId}`,
|
||||
type: 'index-pattern',
|
||||
id: boundaryIndexId as string,
|
||||
},
|
||||
];
|
||||
return {
|
||||
params: {
|
||||
...otherParams,
|
||||
indexRefName: `tracked_index_${indexId}`,
|
||||
boundaryIndexRefName: `boundary_index_${boundaryIndexId}`,
|
||||
},
|
||||
references,
|
||||
};
|
||||
}
|
||||
|
||||
export function injectEntityAndBoundaryIds(
|
||||
params: GeoContainmentExtractedParams,
|
||||
references: SavedObjectReference[]
|
||||
): GeoContainmentParams {
|
||||
const { indexRefName, boundaryIndexRefName, ...otherParams } = params;
|
||||
const { id: indexId = null } = references.find((ref) => ref.name === indexRefName) || {};
|
||||
const { id: boundaryIndexId = null } =
|
||||
references.find((ref) => ref.name === boundaryIndexRefName) || {};
|
||||
if (!indexId) {
|
||||
throw new Error(`Index "${indexId}" not found in references array`);
|
||||
}
|
||||
if (!boundaryIndexId) {
|
||||
throw new Error(`Boundary index "${boundaryIndexId}" not found in references array`);
|
||||
}
|
||||
return {
|
||||
...otherParams,
|
||||
indexId,
|
||||
boundaryIndexId,
|
||||
} as GeoContainmentParams;
|
||||
}
|
||||
|
||||
export function getAlertType(logger: Logger): GeoContainmentAlertType {
|
||||
const alertTypeName = i18n.translate('xpack.stackAlerts.geoContainment.alertTypeTitle', {
|
||||
defaultMessage: 'Tracking containment',
|
||||
|
@ -179,5 +238,18 @@ export function getAlertType(logger: Logger): GeoContainmentAlertType {
|
|||
actionVariables,
|
||||
minimumLicenseRequired: 'gold',
|
||||
isExportable: true,
|
||||
useSavedObjectReferences: {
|
||||
extractReferences: (
|
||||
params: GeoContainmentParams
|
||||
): RuleParamsAndRefs<GeoContainmentExtractedParams> => {
|
||||
return extractEntityAndBoundaryReferences(params);
|
||||
},
|
||||
injectReferences: (
|
||||
params: GeoContainmentExtractedParams,
|
||||
references: SavedObjectReference[]
|
||||
) => {
|
||||
return injectEntityAndBoundaryIds(params, references);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,13 +12,14 @@ import { executeEsQueryFactory, getShapesFilters, OTHER_CATEGORY } from './es_qu
|
|||
import { AlertServices } from '../../../../alerting/server';
|
||||
import {
|
||||
ActionGroupId,
|
||||
GEO_CONTAINMENT_ID,
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentAlertType,
|
||||
GeoContainmentInstanceContext,
|
||||
GeoContainmentState,
|
||||
} from './alert_type';
|
||||
|
||||
import { GEO_CONTAINMENT_ID } from './alert_type';
|
||||
|
||||
export type LatestEntityLocation = GeoContainmentInstanceState;
|
||||
|
||||
// Flatten agg results and get latest locations for each entity
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import { Logger } from 'src/core/server';
|
||||
import { AlertingSetup } from '../../types';
|
||||
import {
|
||||
GeoContainmentParams,
|
||||
GeoContainmentState,
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentInstanceContext,
|
||||
|
@ -17,6 +16,8 @@ import {
|
|||
RecoveryActionGroupId,
|
||||
} from './alert_type';
|
||||
|
||||
import { GeoContainmentExtractedParams, GeoContainmentParams } from './alert_type';
|
||||
|
||||
interface RegisterParams {
|
||||
logger: Logger;
|
||||
alerting: AlertingSetup;
|
||||
|
@ -26,7 +27,7 @@ export function register(params: RegisterParams) {
|
|||
const { logger, alerting } = params;
|
||||
alerting.registerType<
|
||||
GeoContainmentParams,
|
||||
never, // Only use if defining useSavedObjectReferences hook
|
||||
GeoContainmentExtractedParams,
|
||||
GeoContainmentState,
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentInstanceContext,
|
||||
|
|
|
@ -6,7 +6,12 @@
|
|||
*/
|
||||
|
||||
import { loggingSystemMock } from '../../../../../../../src/core/server/mocks';
|
||||
import { getAlertType, GeoContainmentParams } from '../alert_type';
|
||||
import {
|
||||
getAlertType,
|
||||
injectEntityAndBoundaryIds,
|
||||
GeoContainmentParams,
|
||||
extractEntityAndBoundaryReferences,
|
||||
} from '../alert_type';
|
||||
|
||||
describe('alertType', () => {
|
||||
const logger = loggingSystemMock.create().get();
|
||||
|
@ -43,4 +48,94 @@ describe('alertType', () => {
|
|||
|
||||
expect(alertType.validate?.params?.validate(params)).toBeTruthy();
|
||||
});
|
||||
|
||||
test('injectEntityAndBoundaryIds', () => {
|
||||
expect(
|
||||
injectEntityAndBoundaryIds(
|
||||
{
|
||||
boundaryGeoField: 'geometry',
|
||||
boundaryIndexRefName: 'boundary_index_boundaryid',
|
||||
boundaryIndexTitle: 'boundary*',
|
||||
boundaryType: 'entireIndex',
|
||||
dateField: '@timestamp',
|
||||
entity: 'vehicle_id',
|
||||
geoField: 'geometry',
|
||||
index: 'foo*',
|
||||
indexRefName: 'tracked_index_foobar',
|
||||
},
|
||||
[
|
||||
{
|
||||
id: 'foreign',
|
||||
name: 'foobar',
|
||||
type: 'foreign',
|
||||
},
|
||||
{
|
||||
id: 'foobar',
|
||||
name: 'tracked_index_foobar',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
{
|
||||
id: 'foreignToo',
|
||||
name: 'boundary_index_shouldbeignored',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
{
|
||||
id: 'boundaryid',
|
||||
name: 'boundary_index_boundaryid',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
]
|
||||
)
|
||||
).toEqual({
|
||||
index: 'foo*',
|
||||
indexId: 'foobar',
|
||||
geoField: 'geometry',
|
||||
entity: 'vehicle_id',
|
||||
dateField: '@timestamp',
|
||||
boundaryType: 'entireIndex',
|
||||
boundaryIndexTitle: 'boundary*',
|
||||
boundaryIndexId: 'boundaryid',
|
||||
boundaryGeoField: 'geometry',
|
||||
});
|
||||
});
|
||||
|
||||
test('extractEntityAndBoundaryReferences', () => {
|
||||
expect(
|
||||
extractEntityAndBoundaryReferences({
|
||||
index: 'foo*',
|
||||
indexId: 'foobar',
|
||||
geoField: 'geometry',
|
||||
entity: 'vehicle_id',
|
||||
dateField: '@timestamp',
|
||||
boundaryType: 'entireIndex',
|
||||
boundaryIndexTitle: 'boundary*',
|
||||
boundaryIndexId: 'boundaryid',
|
||||
boundaryGeoField: 'geometry',
|
||||
})
|
||||
).toEqual({
|
||||
params: {
|
||||
boundaryGeoField: 'geometry',
|
||||
boundaryIndexRefName: 'boundary_index_boundaryid',
|
||||
boundaryIndexTitle: 'boundary*',
|
||||
boundaryType: 'entireIndex',
|
||||
dateField: '@timestamp',
|
||||
entity: 'vehicle_id',
|
||||
geoField: 'geometry',
|
||||
index: 'foo*',
|
||||
indexRefName: 'tracked_index_foobar',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: 'foobar',
|
||||
name: 'tracked_index_foobar',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
{
|
||||
id: 'boundaryid',
|
||||
name: 'boundary_index_boundaryid',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,11 +17,8 @@ import {
|
|||
getGeoContainmentExecutor,
|
||||
} from '../geo_containment';
|
||||
import { OTHER_CATEGORY } from '../es_query_builder';
|
||||
import {
|
||||
GeoContainmentInstanceContext,
|
||||
GeoContainmentInstanceState,
|
||||
GeoContainmentParams,
|
||||
} from '../alert_type';
|
||||
import { GeoContainmentInstanceContext, GeoContainmentInstanceState } from '../alert_type';
|
||||
import type { GeoContainmentParams } from '../alert_type';
|
||||
|
||||
const alertInstanceFactory =
|
||||
(contextKeys: unknown[], testAlertActionArr: unknown[]) => (instanceId: string) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue